diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index d9fb51e6ca..a68dce1cfd 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1213 +1,1215 @@ /* 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 #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 #ifdef 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::SourceSide, ICore::self()->uiController()->activeMainWindow() ) ) { 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()), i18nc("@title:window", "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; } } } } } } // 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(); #ifdef 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 (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(); return d->documents.value( url, 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( !DocumentController::isEmptyDocumentUrl(doc->url()) && !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::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 = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } void DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { 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( QString target ) const { if(target.size()) { foreach(IProject* project, Core::self()->projectController()->projects()) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->folder().pathOrUrl() + "/."; } } } IDocument* doc = activeDocument(); if(!doc || target == "[selection]") { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && static_cast(selection)->items().size()) { QString ret = static_cast(selection)->items()[0]->url().pathOrUrl(); if(static_cast(selection)->items()[0]->folder()) ret += "/."; return ret; } 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(); + QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->getTopViews(); + 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(); + static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); 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->isSplit()) 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->isSplit()) { kDebug() << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); kDebug() << "WARNING: Area is already split (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()->isSplit()) { kDebug() << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { 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/sublime/areaindex.cpp b/sublime/areaindex.cpp index f99fde8e59..6c84b3db88 100644 --- a/sublime/areaindex.cpp +++ b/sublime/areaindex.cpp @@ -1,252 +1,252 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "areaindex.h" #include #include #include "view.h" #include "document.h" namespace Sublime { // struct AreaIndexPrivate struct AreaIndexPrivate { AreaIndexPrivate() :parent(0), first(0), second(0), orientation(Qt::Horizontal) { } ~AreaIndexPrivate() { delete first; delete second; foreach( View* v, views ) { // Do the same as AreaIndex::remove(), seems like deletion of the view is happening elsewhere views.removeAll( v ); } } AreaIndexPrivate(const AreaIndexPrivate &p) { parent = 0; orientation = p.orientation; first = p.first ? new AreaIndex(*(p.first)) : 0; second = p.second ? new AreaIndex(*(p.second)) : 0; } bool isSplit() const { return first || second; } QList views; AreaIndex *parent; AreaIndex *first; AreaIndex *second; Qt::Orientation orientation; }; // class AreaIndex AreaIndex::AreaIndex() : d(new AreaIndexPrivate) { } AreaIndex::AreaIndex(AreaIndex *parent) : d(new AreaIndexPrivate) { d->parent = parent; } AreaIndex::AreaIndex(const AreaIndex &index) : d(new AreaIndexPrivate( *(index.d) ) ) { kDebug() << "copying area index"; if (d->first) d->first->setParent(this); if (d->second) d->second->setParent(this); //clone views in this index d->views.clear(); foreach (View *view, index.views()) add(view->document()->createView()); } AreaIndex::~AreaIndex() { delete d; } void AreaIndex::add(View *view, View *after) { //we can not add views to the areas that have already been split if (d->isSplit()) return; if (after) d->views.insert(d->views.indexOf(after)+1, view); else d->views.append(view); } void AreaIndex::remove(View *view) { if (d->isSplit()) return; d->views.removeAll(view); if (d->parent && (d->views.count() == 0)) d->parent->unsplit(this); } -void Sublime::AreaIndex::split(Qt::Orientation orientation, bool moveViewsToSecond) +void AreaIndex::split(Qt::Orientation orientation, bool moveViewsToSecond) { //we can not split areas that have already been split if (d->isSplit()) return; d->first = new AreaIndex(this); d->second = new AreaIndex(this); d->orientation = orientation; if(moveViewsToSecond) moveViewsTo(d->second); else moveViewsTo(d->first); } void AreaIndex::split(View *newView, Qt::Orientation orientation) { split(orientation); //make new view as second widget in splitter d->second->add(newView); } void AreaIndex::unsplit(AreaIndex *childToRemove) { if (!d->isSplit()) return; AreaIndex *other = d->first == childToRemove ? d->second : d->first; other->moveViewsTo(this); d->orientation = other->orientation(); d->first = 0; d->second = 0; other->copyChildrenTo(this); delete other; delete childToRemove; } void AreaIndex::copyChildrenTo(AreaIndex *target) { if (!d->first || !d->second) return; target->d->first = d->first; target->d->second = d->second; target->d->first->setParent(target); target->d->second->setParent(target); d->first = 0; d->second = 0; } void AreaIndex::moveViewsTo(AreaIndex *target) { target->d->views = d->views; d->views.clear(); } QList &AreaIndex::views() const { return d->views; } View *AreaIndex::viewAt(int position) const { return d->views.value(position, 0); } int AreaIndex::viewCount() const { return d->views.count(); } bool AreaIndex::hasView(View *view) const { return d->views.contains(view); } AreaIndex *AreaIndex::parent() const { return d->parent; } void AreaIndex::setParent(AreaIndex *parent) { d->parent = parent; } AreaIndex *AreaIndex::first() const { return d->first; } AreaIndex *AreaIndex::second() const { return d->second; } Qt::Orientation AreaIndex::orientation() const { return d->orientation; } bool Sublime::AreaIndex::isSplit() const { return d->isSplit(); } void Sublime::AreaIndex::setOrientation(Qt::Orientation orientation) const { d->orientation = orientation; } // class RootAreaIndex RootAreaIndex::RootAreaIndex() :AreaIndex(), d(0) { } QString AreaIndex::print() const { if(isSplit()) return " [ " + first()->print() + (orientation() == Qt::Horizontal ? " / " : " - ") + second()->print() + " ] "; QStringList ret; foreach(Sublime::View* view, views()) ret << view->document()->title(); return ret.join(" "); } } diff --git a/sublime/mainwindow.cpp b/sublime/mainwindow.cpp index 314ad6ade6..9d55ca6ee6 100644 --- a/sublime/mainwindow.cpp +++ b/sublime/mainwindow.cpp @@ -1,466 +1,485 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow.h" #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "ideal.h" #include "holdupdates.h" namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) : KParts::MainWindow(0, flags), d(new MainWindowPrivate(this, controller)) { connect(this, SIGNAL(destroyed()), controller, SLOT(areaReleased())); loadGeometry(KGlobal::config()->group("Main Window")); d->areaSwitcher = new AreaTabWidget(menuBar()); menuBar()->setCornerWidget(d->areaSwitcher, Qt::TopRightCorner); // don't allow AllowTabbedDocks - that doesn't make sense for "ideal" UI setDockOptions(QMainWindow::AnimatedDocks); } QWidget* MainWindow::customButtonForAreaSwitcher ( Area* /*area*/ ) { return 0; } bool MainWindow::containsView(View* view) const { foreach(Area* area, areas()) if(area->views().contains(view)) return true; return false; } QList< Area* > MainWindow::areas() const { QList< Area* > areas = controller()->areas(const_cast(this)); if(areas.isEmpty()) areas = controller()->defaultAreas(); return areas; } void MainWindow::setupAreaSelector() { disconnect(d->areaSwitcher->tabBar, SIGNAL(currentChanged(int)), d, SLOT(toggleArea(int))); d->areaSwitcher->setUpdatesEnabled(false); d->areaSwitcher->tabBar->clearTabs(); int currentIndex = -1; QList< Area* > areas = this->areas(); QSet hadAreaName; for(int a = 0; a < areas.size(); ++a) { Area* theArea = areas[a]; if(hadAreaName.contains(theArea->objectName())) continue; hadAreaName.insert(theArea->objectName()); if(theArea->objectName() == area()->objectName()) { currentIndex = a; } d->areaSwitcher->tabBar->addCustomTab(theArea->title(), KIcon(theArea->iconName()), currentIndex == a, theArea->objectName(), customButtonForAreaSwitcher(theArea)); } d->areaSwitcher->tabBar->setCurrentIndex(currentIndex); d->areaSwitcher->updateGeometry(); d->areaSwitcher->setUpdatesEnabled(true); connect(d->areaSwitcher->tabBar, SIGNAL(currentChanged(int)), d, SLOT(toggleArea(int))); } QWidget* MainWindow::areaSwitcher() const { return d->areaSwitcher; } MainWindow::~MainWindow() { kDebug() << "destroying mainwindow"; delete d; } -void MainWindow::reconstructViews() +void MainWindow::reconstructViews(QList topViews) { - d->reconstructViews(); + d->reconstructViews(topViews); +} + +QList MainWindow::getTopViews() const +{ + QList topViews; + foreach(View* view, d->area->views()) + { + if(view->hasWidget()) + { + QWidget* widget = view->widget(); + if(widget->parent() && widget->parent()->parent()) + { + Container* container = qobject_cast(widget->parent()->parent()); + if(container->currentWidget() == widget) + topViews << view; + } + } + } + return topViews; } void MainWindow::setArea(Area *area) { if (d->area) disconnect(d->area, 0, this, 0); bool differentArea = (area != d->area); /* All views will be removed from dock area now. However, this does not mean those are removed from area, so prevent slotDockShown from recording those views as no longer shown in the area. */ d->ignoreDockShown = true; if (d->autoAreaSettingsSave && differentArea) saveSettings(); HoldUpdates hu(this); if (d->area) clearArea(); d->area = area; d->reconstruct(); if(d->area->activeView()) activateView(d->area->activeView()); else d->activateFirstVisibleView(); initializeStatusBar(); emit areaChanged(area); d->ignoreDockShown = false; hu.stop(); loadSettings(); connect(area, SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(viewAdded(Sublime::AreaIndex*,Sublime::View*))); connect(area, SIGNAL(viewRemoved(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(viewRemovedInternal(Sublime::AreaIndex*,Sublime::View*))); connect(area, SIGNAL(requestToolViewRaise(Sublime::View*)), this, SLOT(raiseToolView(Sublime::View*))); connect(area, SIGNAL(aboutToRemoveView(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(aboutToRemoveView(Sublime::AreaIndex*,Sublime::View*))); connect(area, SIGNAL(toolViewAdded(Sublime::View*,Sublime::Position)), this, SLOT(toolViewAdded(Sublime::View*,Sublime::Position))); connect(area, SIGNAL(aboutToRemoveToolView(Sublime::View*,Sublime::Position)), this, SLOT(aboutToRemoveToolView(Sublime::View*,Sublime::Position))); connect(area, SIGNAL(toolViewMoved(Sublime::View*,Sublime::Position)), this, SLOT(toolViewMoved(Sublime::View*,Sublime::Position))); connect(area, SIGNAL(changedWorkingSet(Sublime::Area*,QString,QString)), this, SLOT(setupAreaSelector())); } void MainWindow::initializeStatusBar() { //nothing here, reimplement in the subclasses if you want to have status bar //inside the bottom toolview buttons row } void MainWindow::resizeEvent(QResizeEvent* event) { return KParts::MainWindow::resizeEvent(event); } void MainWindow::clearArea() { emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { return d->docks; } Area *Sublime::MainWindow::area() const { return d->area; } Controller *MainWindow::controller() const { return d->controller; } View *MainWindow::activeView() { return d->activeView; } View *MainWindow::activeToolView() { return d->activeToolView; } void MainWindow::activateView(View *view) { if (!d->viewContainers.contains(view)) return; d->viewContainers[view]->setCurrentWidget(view->widget()); setActiveView(view); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view) { View* oldActiveView = d->activeView; d->activeView = view; if (view && !view->widget()->hasFocus()) view->widget()->setFocus(); if(d->activeView != oldActiveView) emit activeViewChanged(view); } void Sublime::MainWindow::setActiveToolView(View *view) { d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { QString group = "MainWindow"; if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KGlobal::config()->group(group); /* This will try to save window size, too. But it's OK, since we won't use this information when loading. */ saveMainWindowSettings(cg); //debugToolBar visibility is stored separately to allow a area dependent default value foreach (KToolBar* toolbar, toolBars()) { if (toolbar->objectName() == "debugToolBar") { cg.writeEntry("debugToolBarVisibility", toolbar->isVisibleTo(this)); } } cg.sync(); } void MainWindow::loadSettings() { HoldUpdates hu(this); kDebug(9504) << "loading settings for " << (area() ? area()->objectName() : ""); QString group = "MainWindow"; if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KGlobal::config()->group(group); // What follows is copy-paste from applyMainWindowSettings. Unfortunately, // we don't really want that one to try restoring window size, and we also // cannot stop it from doing that in any clean way. QStatusBar* sb = qFindChild(this); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); if ( entry == "Disabled" ) sb->hide(); else sb->show(); } QMenuBar* mb = qFindChild(this); if (mb) { QString entry = cg.readEntry ("MenuBar", "Enabled"); if ( entry == "Disabled" ) mb->hide(); else mb->show(); } if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { QString entry = cg.readEntry ("ToolBarsMovable", "Enabled"); if ( entry == "Disabled" ) KToolBar::setToolBarsLocked(true); else KToolBar::setToolBarsLocked(false); } // Utilise the QMainWindow::restoreState() functionality // Note that we're fixing KMainWindow bug here -- the original // code has this fragment above restoring toolbar properties. // As result, each save/restore would move the toolbar a bit to // the left. if (cg.hasKey("State")) { QByteArray state; state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 restoreState(state); } else { // If there's no state we use a default size of 870x650 // Resize only when showing "code" area. If we do that for other areas, // then we'll hit bug https://bugs.kde.org/show_bug.cgi?id=207990 // TODO: adymo: this is more like a hack, we need a proper first-start initialization if (area() && area()->objectName() == "code") resize(870,650); } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar* toolbar, toolBars()) { QString group("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars group += (toolbar->objectName().isEmpty() ? QString::number(n) : QString(" ")+toolbar->objectName()); KConfigGroup toolbarGroup(&cg, group); toolbar->applySettings(toolbarGroup, false); if (toolbar->objectName() == "debugToolBar") { //debugToolBar visibility is stored separately to allow a area dependent default value bool visibility = cg.readEntry("debugToolBarVisibility", area()->objectName() == "debug"); toolbar->setVisible(visibility); } n++; } KConfigGroup uiGroup = KGlobal::config()->group("UiSettings"); foreach (Container *container, findChildren()) { container->setTabBarHidden(uiGroup.readEntry("TabBarVisibility", 1) == 0); } cg.sync(); hu.stop(); emit settingsLoaded(); } bool MainWindow::queryClose() { // saveSettings(); KConfigGroup config(KGlobal::config(), "Main Window"); saveGeometry(config); config.sync(); return KParts::MainWindow::queryClose(); } void MainWindow::saveGeometry(KConfigGroup &config) { int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); QString key = QString::fromLatin1("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); config.writeEntry(key, geometry()); } void MainWindow::loadGeometry(const KConfigGroup &config) { // The below code, essentially, is copy-paste from // KMainWindow::restoreWindowSize. Right now, that code is buggy, // as per http://permalink.gmane.org/gmane.comp.kde.devel.core/52423 // so we implement a less theoretically correct, but working, version // below const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); QString key = QString::fromLatin1("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); QRect g = config.readEntry(key, QRect()); if (!g.isEmpty()) setGeometry(g); } void MainWindow::enableAreaSettingsSave() { d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() { return d->idealController->statusBarLocation(); } void MainWindow::setAreaSwitcherCornerWidget(QWidget* widget) { d->areaSwitcher->setTabSideWidget(widget); } void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabContextMenuRequested(View* , KMenu* ) { // do nothing } void MainWindow::tabToolTipRequested(View*, Container*, int) { // do nothing } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea , const QPoint& ) { // do nothing } View* MainWindow::viewForPosition(QPoint globalPos) const { foreach(Container* container, d->viewContainers.values()) { QRect globalGeom = QRect(container->mapToGlobal(QPoint(0,0)), container->mapToGlobal(QPoint(container->width(), container->height()))); if(globalGeom.contains(globalPos)) { return d->widgetToView[container->currentWidget()]; } } return 0; } void MainWindow::setBackgroundCentralWidget(QWidget* w) { d->setBackgroundCentralWidget(w); } } #include "mainwindow.moc" diff --git a/sublime/mainwindow.h b/sublime/mainwindow.h index e8f590c2f6..82676baa45 100644 --- a/sublime/mainwindow.h +++ b/sublime/mainwindow.h @@ -1,188 +1,191 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef SUBLIMEMAINWINDOW_H #define SUBLIMEMAINWINDOW_H #include #include #include "sublimeexport.h" class QDockWidget; namespace Sublime { class Container; class Area; class View; class Controller; class MainWindowOperator; /** @short Sublime Main Window The area-enabled mainwindow to show Sublime views and toolviews. To use, a controller and constructed areas are necessary: @code MainWindow w(controller); controller->showArea(area, &w); @endcode */ class SUBLIME_EXPORT MainWindow: public KParts::MainWindow { Q_OBJECT public: /**Creates a mainwindow and adds it to the controller.*/ explicit MainWindow(Controller *controller, Qt::WindowFlags flags = KDE_DEFAULT_WINDOWFLAGS); ~MainWindow(); /**@return the list of dockwidgets that contain area's toolviews.*/ QList toolDocks() const; /**@return area which mainwindow currently shows or 0 if no area has been set.*/ Area *area() const; /**@return controller for this mainwindow.*/ Controller *controller() const; /**@return active view inside this mainwindow.*/ View *activeView(); /**@return active toolview inside this mainwindow.*/ View *activeToolView(); /**Enable saving of per-area UI settings (like toolbar properties and position) whenever area is changed. This should be called after all areas are restored, and main window area is set, to prevent saving a half-broken state. */ void enableAreaSettingsSave(); /** Allows setting an additional widget that will be inserted next to the area-switcher tabs */ void setAreaSwitcherCornerWidget(QWidget* widget); /** Allows setting an additional widget that will be inserted left to the document tab-bar. * The ownership goes to the target. */ void setTabBarLeftCornerWidget(QWidget* widget); /**Sets the area of main window and fills it with views. *The contents is reconstructed, even if the area equals the currently set area. */ void setArea(Area *area); /** * Reconstruct the view structure. This is required after significant untracked changes to the * area-index structure. + * Views listed in topViews will be on top of their view stacks. * */ - void reconstructViews(); + void reconstructViews(QList topViews = QList()); - /**Returns the view that is closest to the given global position, or zero. - * */ + /**Returns a list of all views which are on top of their corresponding view stacks*/ + QList getTopViews() const; + + /**Returns the view that is closest to the given global position, or zero.*/ View* viewForPosition(QPoint globalPos) const; /**Returns true if this main-window contains this view*/ bool containsView(View* view) const; /**Returns all areas that belong to this main-window*/ QList areas() const; /** Sets a @p w widget that will be shown when there are no opened documents. * This method takes the ownership of @p w. */ void setBackgroundCentralWidget(QWidget* w); public Q_SLOTS: /**Shows the @p view and makes it active.*/ void activateView(Sublime::View *view); /**Loads size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to load more and don't forget to call inherited method.*/ virtual void loadSettings(); Q_SIGNALS: /**Emitted before the area is cleared from this mainwindow.*/ void areaCleared(Sublime::Area*); /**Emitted after the new area has been shown in this mainwindow.*/ void areaChanged(Sublime::Area*); /**Emitted when the active view is changed.*/ void activeViewChanged(Sublime::View*); /**Emitted when the active toolview is changed.*/ void activeToolViewChanged(Sublime::View*); /**Emitted when the user interface settings have changed.*/ void settingsLoaded(); /**Emitted when a new view is added to the mainwindow.*/ void viewAdded(Sublime::View*); /**Emitted when a view is going to be removed from the mainwindow.*/ void aboutToRemoveView(Sublime::View*); protected: QWidget *statusBarLocation(); virtual void initializeStatusBar(); protected Q_SLOTS: virtual void setupAreaSelector(); virtual void tabContextMenuRequested(Sublime::View*, KMenu*); virtual void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab); /**Called whenever the user requests a context menu on a dockwidget bar. You can then e.g. add actions to add dockwidgets. Default implementation does nothing.**/ virtual void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&); public: // FIXME? /**Saves size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to save more and don't forget to call inherited method.*/ virtual void saveSettings(); /**Reimplemented to save settings.*/ virtual bool queryClose(); /**Reimplement this to add custom buttons into the area switchers. The default-implementation returns zero, which means that no button is added *The returned button is owned by the caller, and deleted at will. */ virtual QWidget* customButtonForAreaSwitcher ( Area* area ); QWidget* areaSwitcher() const; private: Q_PRIVATE_SLOT(d, void viewAdded(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void viewRemovedInternal(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void aboutToRemoveView(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void toolViewAdded(Sublime::View*, Sublime::Position)) Q_PRIVATE_SLOT(d, void raiseToolView(Sublime::View*)) Q_PRIVATE_SLOT(d, void aboutToRemoveToolView(Sublime::View*, Sublime::Position)) Q_PRIVATE_SLOT(d, void toolViewMoved(Sublime::View*, Sublime::Position)) //Inherit MainWindowOperator to access four methods below /**Unsets the area clearing main window.*/ void clearArea(); /**Sets the active view and focuses it.*/ void setActiveView(Sublime::View *view); /**Sets the active toolview and focuses it.*/ void setActiveToolView(View *view); void resizeEvent(QResizeEvent* event); void saveGeometry(KConfigGroup &config); void loadGeometry(const KConfigGroup &config); class MainWindowPrivate *const d; friend class MainWindowOperator; friend class MainWindowPrivate; }; } #endif diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 1599d56db2..39933819be 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,901 +1,909 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "ideal.h" #include "holdupdates.h" #include #include #include class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent), m_buttons(buttons) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); } void hideWhenEmpty() { refresh(); connect(this, SIGNAL(visibilityChanged(bool)), SLOT(refresh())); connect(m_buttons, SIGNAL(emptyChanged()), SLOT(refresh())); } public slots: void refresh() { if(m_buttons->isEmpty()==isVisible()) { setVisible(!m_buttons->isEmpty()); } } private: Sublime::IdealButtonBarWidget* m_buttons; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(0), activeView(0), activeToolView(0), bgCentralWidget(0), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); KAction* action = new KAction(i18n("Show Left Dock"), this); action->setCheckable(true); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_L); connect(action, SIGNAL(toggled(bool)), SLOT(showLeftDock(bool))); ac->addAction("show_left_dock", action); action = new KAction(i18n("Show Right Dock"), this); action->setCheckable(true); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_R); connect(action, SIGNAL(toggled(bool)), SLOT(showRightDock(bool))); ac->addAction("show_right_dock", action); action = new KAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_B); connect(action, SIGNAL(toggled(bool)), SLOT(showBottomDock(bool))); ac->addAction("show_bottom_dock", action); action = new KAction(i18nc("@action", "Focus Editor"), this); action->setShortcuts(QList() << (Qt::META | Qt::CTRL | Qt::Key_E) << Qt::META + Qt::Key_C); connect(action, SIGNAL(triggered(bool)), this, SLOT(focusEditor())); ac->addAction("focus_editor", action); action = new KAction(i18n("Hide/Restore Docks"), this); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_H); connect(action, SIGNAL(triggered(bool)), SLOT(toggleDocksShown())); ac->addAction("hide_all_docks", action); action = new KAction(i18n("Next Tool View"), this); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_N); connect(action, SIGNAL(triggered(bool)), SLOT(selectNextDock())); ac->addAction("select_next_dock", action); action = new KAction(i18n("Previous Tool View"), this); action->setShortcut(Qt::META | Qt::CTRL | Qt::Key_P); connect(action, SIGNAL(triggered(bool)), SLOT(selectPreviousDock())); ac->addAction("select_previous_dock", action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction("docks_submenu", action); idealController = new IdealController(m_mainWindow); IdealToolBar* leftToolBar = new IdealToolBar(i18n("Left Button Bar"), idealController->leftBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::LeftToolBarArea, leftToolBar); IdealToolBar* rightToolBar = new IdealToolBar(i18n("Right Button Bar"), idealController->rightBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::RightToolBarArea, rightToolBar); IdealToolBar* bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(centralWidget); centralWidget->setLayout(layout); layout->setMargin(0); splitterCentralWidget = new QSplitter(centralWidget); layout->addWidget(splitterCentralWidget); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, SIGNAL(dockShown(Sublime::View*,Sublime::Position,bool)), this, SLOT(slotDockShown(Sublime::View*,Sublime::Position,bool))); connect(idealController, SIGNAL(widgetResized(Qt::DockWidgetArea,int)), this, SLOT(widgetResized(Qt::DockWidgetArea,int))); connect(idealController, SIGNAL(dockBarContextMenuRequested(Qt::DockWidgetArea,QPoint)), m_mainWindow, SLOT(dockBarContextMenuRequested(Qt::DockWidgetArea,QPoint))); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { kDebug() << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; d->centralWidget->layout()->addWidget(splitter); } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = 0; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); + kDebug() << "deleting" << widget; widget->setParent(0); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, SIGNAL(activateView(Sublime::View*)), d->m_mainWindow, SLOT(activateView(Sublime::View*))); connect(container, SIGNAL(tabContextMenuRequested(Sublime::View*,KMenu*)), d->m_mainWindow, SLOT(tabContextMenuRequested(Sublime::View*,KMenu*))); connect(container, SIGNAL(tabToolTipRequested(Sublime::View*,Sublime::Container*,int)), d->m_mainWindow, SLOT(tabToolTipRequested(Sublime::View*,Sublime::Container*,int))); connect(container, SIGNAL(closeRequest(QWidget*)), d, SLOT(widgetCloseRequest(QWidget*)), Qt::QueuedConnection); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; + bool hadActiveView = false; + foreach (View *view, index->views()) { QWidget *widget = view->widget(container); + if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(d->activeView == view) + { + hadActiveView = true; + container->setCurrentWidget(widget); + }else if(topViews.contains(view)) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } -void MainWindowPrivate::reconstructViews() +void MainWindowPrivate::reconstructViews(QList topViews) { - ViewCreator viewCreator(this); + ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget.data()->hide(); m_leftTabbarCornerWidget.data()->setParent(0); } idealController->setWidthForArea(Qt::LeftDockWidgetArea, area->thickness(Sublime::Left)); idealController->setWidthForArea(Qt::BottomDockWidgetArea, area->thickness(Sublime::Bottom)); idealController->setWidthForArea(Qt::RightDockWidgetArea, area->thickness(Sublime::Right)); IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); m_mainWindow->blockSignals(true); kDebug() << "RECONSTRUCT" << area << " " << area->shownToolViews(Sublime::Left) << "\n"; foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } m_mainWindow->blockSignals(false); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget.data()->setParent(0); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(0); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(0); } cleanCentralWidget(); m_mainWindow->setActiveView(0); m_indexSplitters.clear(); area = 0; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); // A formerly non-empty working-set has become empty, and a relayout of the area-selector may be required if(m_mainWindow->area()->views().size() == 0) m_mainWindow->setupAreaSelector(); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget.data()->hide(); m_leftTabbarCornerWidget.data()->setParent(0); } { // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(0); } //and then delete the container delete container; } } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); // A formerly empty working-set may become non-empty, and a relayout of the area-selector may be required if(m_mainWindow->area()->views().size() == 1) m_mainWindow->setupAreaSelector(); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { if (!m_indexSplitters[index]) return; QSplitter *splitter = m_indexSplitters[index]; kDebug() << "index " << index << " root " << area->rootIndex(); kDebug() << "splitter " << splitter << " container " << splitter->widget(0); kDebug() << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { kWarning() << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(0); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget.data()->hide(); m_leftTabbarCornerWidget.data()->setParent(0); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(0); else kWarning() << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(0); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ QCommonStyle* tmpStyle = new QCommonStyle; // temp style since container uses it's parents style, which gets zeroed container->setStyle(tmpStyle); connect(container, SIGNAL(destroyed(QObject*)), tmpStyle, SLOT(deleteLater())); container->setParent(0); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } kDebug() << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(0L); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { kDebug() << "for" << action; controller->showArea(m_actionAreas[action], m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { if (m_areaActions.contains(area)) m_areaActions[area]->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { if (area->views().count() > 0) m_mainWindow->activateView(area->views().first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (widgetToView.contains(widget)) { View *view = widgetToView[widget]; area->closeView(view); } } void MainWindowPrivate::toggleArea ( int index ) { m_mainWindow->controller()->showArea(areaSwitcher->tabBar->areaId(index), m_mainWindow); } void AreaTabBar::paintEvent ( QPaintEvent* ev ) { QTabBar::paintEvent(ev); if ( currentIndex() != -1 ) { QStylePainter p ( this ); //Draw highlight behind current area QRect activeRect = tabRect ( currentIndex() ); QRect tabArea = activeRect; QImage img ( tabArea.width(), tabArea.height(), QImage::Format_ARGB32 ); img.fill ( 0 ); QPainter paintImg ( &img ); QColor color ( palette().color ( QPalette::Active, QPalette::Highlight ) ); color.setAlpha ( 70 ); QRect paint = tabArea; const int margin = 8; paint.setLeft ( margin ); paint.setTop ( margin ); paint.setRight ( activeRect.width()-margin ); paint.setBottom ( activeRect.height()-margin ); paintImg.fillRect ( paint, color ); expblur<16,7> ( img, margin/2 ); p.drawImage ( tabArea, img ); } } void AreaTabWidget::paintEvent ( QPaintEvent* ev ) { QWidget::paintEvent ( ev ); if ( tabBar->isVisible() && tabBar->count() > 0 ) { QStylePainter p ( this ); QStyleOptionTabBarBase optTabBase; optTabBase.init ( tabBar ); optTabBase.shape = tabBar->shape(); optTabBase.tabBarRect = tabBar->rect(); optTabBase.tabBarRect.moveTopLeft(tabBar->pos()); int sideWidth = width()-tabBar->width(); QStyleOptionTab tabOverlap; tabOverlap.shape = tabBar->shape(); int overlap = style()->pixelMetric ( QStyle::PM_TabBarBaseOverlap, &tabOverlap, tabBar ); QRect rect; rect.setRect ( 0, height()-overlap, sideWidth, overlap ); optTabBase.rect = rect; QImage img ( sideWidth, height(), QImage::Format_ARGB32 ); img.fill ( 0 ); QStylePainter p2 ( &img, tabBar ); p2.drawPrimitive ( QStyle::PE_FrameTabBarBase, optTabBase ); { //Blend the painted line out to the left QLinearGradient blendOut ( 0, 0, sideWidth, 0 ); blendOut.setColorAt ( 0, QColor ( 255, 255, 255, 255 ) ); blendOut.setColorAt ( 1, QColor ( 0, 0, 0, 0 ) ); QBrush blendBrush ( blendOut ); p2.setCompositionMode ( QPainter::CompositionMode_DestinationOut ); p2.fillRect ( img.rect(), blendBrush ); } p.setCompositionMode ( QPainter::CompositionMode_SourceOver ); p.drawImage ( img.rect(), img ); } } QSize AreaTabWidget::sizeHint() const { QMenuBar* menuBar = qobject_cast(parent()); if ( !menuBar ) { return QWidget::sizeHint(); } //Resize to hold the whole length up to the menu static bool zeroSizeHint = false; if ( zeroSizeHint ) return QSize(); zeroSizeHint = true; int available = menuBar->parentWidget()->width() - menuBar->sizeHint().width() - 10; zeroSizeHint = false; QSize orig = tabBar->sizeHint(); int addFade = available - orig.width(); int perfectFade = 500; if(areaSideWidget) perfectFade += areaSideWidget->sizeHint().width(); if(addFade > perfectFade) addFade = perfectFade; int wantWidth = addFade + orig.width(); if ( wantWidth > orig.width() ) orig.setWidth ( wantWidth ); return orig; } AreaTabWidget::AreaTabWidget ( QWidget* parent ) : QWidget ( parent ), areaSideWidget(0) { m_layout = new QHBoxLayout ( this ); m_layout->setAlignment ( Qt::AlignRight ); tabBar = new AreaTabBar ( this ); m_layout->addWidget ( tabBar ); m_layout->setContentsMargins ( 0,0,0,0 ); m_leftLayout = new QVBoxLayout; m_leftLayout->setContentsMargins(11, 4, 11, 8); ///@todo These margins are a bit too hardcoded, should depend on style m_layout->insertLayout(0, m_leftLayout); } QSize AreaTabBar::tabSizeHint ( int index ) const { //Since we move all contents into the button, we give approximately the button size as size-hint //QTabBar seems to add useless space for the non-existing tab text QSize ret = QTabBar::tabSizeHint(index); if(ret.width() > buttons[index]->sizeHint().width()+16) ret.setWidth(buttons[index]->sizeHint().width() + 16); ///@todo Where does the offset come from? return ret; } void AreaTabButton::setIsCurrent ( bool arg1 ) { m_isCurrent = arg1; } void AreaTabBar::setCurrentIndex ( int index ) { m_currentIndex = index; foreach(AreaTabButton* button, buttons) { button->setIsCurrent(buttons.indexOf(button) == index); } QTabBar::setCurrentIndex ( index ); foreach(AreaTabButton* button, buttons) { button->update(); } update(); } AreaTabBar::AreaTabBar ( QWidget* parent ) : QTabBar ( parent ), m_currentIndex ( -1 ) { setShape ( QTabBar::RoundedNorth ); setDocumentMode ( true ); setExpanding ( false ); setLayoutDirection ( Qt::RightToLeft ); setDrawBase ( false ); setUsesScrollButtons ( false ); setFocusPolicy( Qt::NoFocus ); QPalette pal = palette(); } AreaTabButton::AreaTabButton ( QString text, QIcon icon, uint iconSize, QWidget* parent, bool isCurrent, QWidget* _customButtonWidget ) : QWidget ( parent ), customButtonWidget(_customButtonWidget), m_isCurrent ( isCurrent ) { QHBoxLayout* layout = new QHBoxLayout ( this ); iconLabel = new QLabel ( this ); iconLabel->setPixmap ( icon.pixmap ( QSize ( iconSize, iconSize ) ) ); iconLabel->setAutoFillBackground ( false ); textLabel = new QLabel ( this ); textLabel->setText ( text ); textLabel->setAutoFillBackground ( false ); if(customButtonWidget) { customButtonWidget->setParent(this); layout->addWidget(customButtonWidget); } layout->addWidget ( textLabel ); layout->addWidget ( iconLabel ); layout->setMargin ( 0 ); } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = 0; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = viewContainers.values()[0]; } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/sublime/mainwindow_p.h b/sublime/mainwindow_p.h index 4af58d53b0..a9b6342938 100644 --- a/sublime/mainwindow_p.h +++ b/sublime/mainwindow_p.h @@ -1,232 +1,233 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program 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 program 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 General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef SUBLIMEMAINWINDOW_P_H #define SUBLIMEMAINWINDOW_P_H #include #include #include #include #include #include #include "area.h" #include "sublimedefs.h" #include "mainwindow.h" #include #include #include #include #include #include "blur.h" class QMenu; class QAction; class QSplitter; class QDockWidget; class QComboBox; namespace Sublime { class View; class Container; class Controller; class AreaIndex; class IdealMainWidget; class IdealController; class AreaTabButton : public QWidget { public: AreaTabButton(QString text, QIcon icon, uint iconSize, QWidget* parent, bool isCurrent, QWidget* _customButtonWidget) ; QLabel* iconLabel; QLabel* textLabel; QWidget* customButtonWidget; void setIsCurrent ( bool arg1 ); private: bool m_isCurrent; }; class AreaTabBar : public QTabBar { public: AreaTabBar(QWidget* parent) ; //Non-vertual overload: Respected by mainwindow.cpp void setCurrentIndex(int index) ; virtual QSize tabSizeHint ( int index ) const ; void clearTabs() { while(count()) removeTab(0); buttons.clear(); areaIds.clear(); } QString areaId(int index) const { return areaIds[index]; } void addCustomTab(QString text, QIcon icon, bool isCurrent, QString areaId, QWidget* customButtonWidget) { // plain sublime doens't provide customButtonWidget, only kdevelop shell does // TODO: adymo: refactor if (customButtonWidget) { customButtonWidget->setParent(this); customButtonWidget->show(); } areaIds << areaId; buttons << new AreaTabButton(text, icon, 16, this, isCurrent, customButtonWidget); addTab(QString()); setTabButton(count()-1, LeftSide, buttons.last()); } virtual void paintEvent ( QPaintEvent* ); private: QList areaIds; QList buttons; int m_currentIndex; }; class AreaTabWidget : public QWidget { public: AreaTabWidget(QWidget* parent = 0) ; virtual QSize sizeHint() const ; virtual void paintEvent(QPaintEvent *ev); ///The widget is owned by this tab-widget void setTabSideWidget(QWidget* widget) { if(areaSideWidget) delete areaSideWidget; areaSideWidget = widget; m_leftLayout->insertWidget(0, areaSideWidget); } AreaTabBar* tabBar; QWidget* areaSideWidget; QHBoxLayout* m_layout; QVBoxLayout* m_leftLayout; }; class MainWindowPrivate: public QObject { Q_OBJECT public: MainWindowPrivate(MainWindow *w, Controller* controller); ~MainWindowPrivate(); /**Use this to create tool views for an area.*/ class IdealToolViewCreator { public: IdealToolViewCreator(MainWindowPrivate *_d): d(_d) {} Area::WalkerMode operator() (View *view, Sublime::Position position); private: MainWindowPrivate *d; }; /**Use this to create views for an area.*/ class ViewCreator { public: - ViewCreator(MainWindowPrivate *_d): d(_d) {} + ViewCreator(MainWindowPrivate *_d, QList _topViews = QList()): d(_d), topViews(_topViews.toSet()) {} Area::WalkerMode operator() (AreaIndex *index); private: MainWindowPrivate *d; + QSet topViews; }; /**Reconstructs the mainwindow according to the current area.*/ void reconstruct(); /**Reconstructs the views according to the current area index.*/ - void reconstructViews(); + void reconstructViews(QList topViews = QList()); /**Clears the area leaving mainwindow empty.*/ void clearArea(); /** Sets a @p w widget that will be shown when there are no documents on the area */ void setBackgroundCentralWidget(QWidget* w); void activateFirstVisibleView(); Controller *controller; Area *area; QList docks; QMap viewContainers; QMap widgetToView; View *activeView; View *activeToolView; QWidget *centralWidget; QWidget* bgCentralWidget; QSplitter* splitterCentralWidget; AreaTabWidget* areaSwitcher; IdealController *idealController; int ignoreDockShown; bool autoAreaSettingsSave; public slots: void viewAdded(Sublime::AreaIndex *index, Sublime::View *view); void viewRemovedInternal(Sublime::AreaIndex *index, Sublime::View *view); void raiseToolView(Sublime::View* view); void aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view); void toolViewAdded(Sublime::View *toolView, Sublime::Position position); void aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position position); void toolViewMoved(Sublime::View *toolView, Sublime::Position position); void toggleArea(int index); void setTabBarLeftCornerWidget(QWidget* widget); private slots: void switchToArea(QAction *action); void updateAreaSwitcher(Sublime::Area *area); void slotDockShown(Sublime::View*, Sublime::Position, bool); void widgetResized(Qt::DockWidgetArea dockArea, int thickness); void widgetCloseRequest(QWidget* widget); void showLeftDock(bool b); void showRightDock(bool b); void showBottomDock(bool b); void focusEditor(); void toggleDocksShown(); void selectNextDock(); void selectPreviousDock(); private: void setBackgroundVisible(bool v); Qt::DockWidgetArea positionToDockArea(Position position); void cleanCentralWidget(); MainWindow *m_mainWindow; // uses QPointer to make already-deleted splitters detectable QMap > m_indexSplitters; friend class AreaSelectionAction; QMap m_areaActions; QMap m_actionAreas; QWeakPointer m_leftTabbarCornerWidget; }; } #endif