diff --git a/shell/partdocument.cpp b/shell/partdocument.cpp index 61cce72fa6..89c5417a9e 100644 --- a/shell/partdocument.cpp +++ b/shell/partdocument.cpp @@ -1,223 +1,218 @@ /*************************************************************************** * Copyright 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 "partdocument.h" #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "partcontroller.h" #include "documentcontroller.h" namespace KDevelop { class PartDocumentPrivate { public: QMap partForView; QString preferredPart; }; PartDocument::PartDocument(const KUrl& url, KDevelop::ICore* core, const QString& preferredPart) : Sublime::UrlDocument(core->uiController()->controller(), url), KDevelop::IDocument(core), d(new PartDocumentPrivate) { d->preferredPart = preferredPart; } PartDocument::~PartDocument() { delete d; } QWidget *PartDocument::createViewWidget(QWidget */*parent*/) { KParts::Part *part = Core::self()->partControllerInternal()->createPart(url(), d->preferredPart); if( part ) { Core::self()->partController()->addPart(part); QWidget *w = part->widget(); d->partForView[w] = part; return w; } return 0; } KParts::Part *PartDocument::partForView(QWidget *view) const { return d->partForView[view]; } //KDevelop::IDocument implementation KMimeType::Ptr PartDocument::mimeType() const { return KMimeType::findByUrl(url()); } KTextEditor::Document *PartDocument::textDocument() const { return 0; } bool PartDocument::isActive() const { return Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()->document() == this; } bool PartDocument::save(DocumentSaveMode /*mode*/) { //part document is read-only so do nothing here return true; } +bool PartDocument::askForCloseFeedback() +{ + if (state() == IDocument::Modified) { + int code = KMessageBox::warningYesNoCancel( + Core::self()->uiController()->activeMainWindow(), + i18n("The document \"%1\" has unsaved changes. Would you like to save them?", url().toLocalFile()), + i18n("Close Document")); + + if (code == KMessageBox::Yes) { + if (!save(Default)) + return false; + + } else if (code == KMessageBox::Cancel) { + return false; + } + + /// @todo Is this behavior right? + } else if (state() == IDocument::DirtyAndModified) { + if (!save(Default)) + return false; + } + return true; +} + bool PartDocument::close(DocumentSaveMode mode) { if (!(mode & Discard)) { if (mode & Silent) { if (!save(mode)) return false; - } else { - if (state() == IDocument::Modified) { - int code = KMessageBox::warningYesNoCancel( - Core::self()->uiController()->activeMainWindow(), - i18n("The document \"%1\" has unsaved changes. Would you like to save them?", url().toLocalFile()), - i18n("Close Document")); - - if (code == KMessageBox::Yes) { - if (!save(mode)) - return false; - - } else if (code == KMessageBox::Cancel) { - return false; - } - - } else if (state() == IDocument::DirtyAndModified) { - if (!save(mode)) - return false; - } + if( !askForCloseFeedback() ) + return false; } } - //close all views and then delete ourself - ///@todo test this - foreach (Sublime::Area *area, - Core::self()->uiControllerInternal()->allAreas()) - { - QList areaViews = area->views(); - foreach (Sublime::View *view, areaViews) { - if (views().contains(view)) { - area->removeView(view); - delete view; - } - } - } + closeViews(); foreach (KParts::Part* part, d->partForView) part->deleteLater(); // The document will be deleted automatically if there are no views left return true; } bool PartDocument::closeDocument(bool silent) { return close(silent ? Silent : Default); } void PartDocument::reload() { //part document is read-only so do nothing here } IDocument::DocumentState PartDocument::state() const { return Clean; } void PartDocument::activate(Sublime::View *activeView, KParts::MainWindow *mainWindow) { Q_UNUSED(mainWindow); KParts::Part *part = partForView(activeView->widget()); if (Core::self()->partController()->activePart() != part) Core::self()->partController()->setActivePart(part); notifyActivated(); } KTextEditor::Cursor KDevelop::PartDocument::cursorPosition() const { return KTextEditor::Cursor::invalid(); } void PartDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { //do nothing here Q_UNUSED(cursor); } void PartDocument::setTextSelection(const KTextEditor::Range &range) { Q_UNUSED(range); } KUrl PartDocument::url() const { return Sublime::UrlDocument::url(); } void PartDocument::setUrl(const KUrl& newUrl) { Sublime::UrlDocument::setUrl(newUrl); if(!prettyName().isEmpty()) setTitle(prettyName()); notifyUrlChanged(); } void PartDocument::setPrettyName(QString name) { KDevelop::IDocument::setPrettyName(name); // Re-set the url, to trigger the whole chain if(!name.isEmpty()) setTitle(name); else setTitle(url().fileName()); } QMap PartDocument::partForView() const { return d->partForView; } void PartDocument::addPartForView(QWidget* w, KParts::Part* p) { d->partForView[w]=p; } } #include "partdocument.moc" diff --git a/shell/partdocument.h b/shell/partdocument.h index c13cf9890a..7b7934e322 100644 --- a/shell/partdocument.h +++ b/shell/partdocument.h @@ -1,87 +1,88 @@ /*************************************************************************** * Copyright 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 KDEV_PARTDOCUMENT_H #define KDEV_PARTDOCUMENT_H #include #include #include "shellexport.h" namespace KParts { class Part; } namespace KDevelop { /** The generic document which represents KParts. This document is used by shell when more specific document classes are incapable of loading the url. This document loads one KPart (read-only or read-write) per view and sets part widget to be a view widget. */ class KDEVPLATFORMSHELL_EXPORT PartDocument: public Sublime::UrlDocument, public KDevelop::IDocument { Q_OBJECT public: PartDocument(const KUrl &url, ICore* core, const QString& preferredPart = QString() ); virtual ~PartDocument(); virtual KUrl url() const; void setUrl(const KUrl& newUrl); virtual QWidget *createViewWidget(QWidget *parent = 0); virtual KParts::Part *partForView(QWidget *view) const; virtual KMimeType::Ptr mimeType() const; virtual KTextEditor::Document* textDocument() const; virtual bool save(DocumentSaveMode mode = Default); virtual void reload(); ///Closes and deletes the document. Asks the user before if needed. virtual bool close(DocumentSaveMode mode = Default); virtual bool isActive() const; virtual DocumentState state() const; virtual void setPrettyName(QString name); virtual void activate(Sublime::View *activeView, KParts::MainWindow *mainWindow); virtual KTextEditor::Cursor cursorPosition() const; virtual void setCursorPosition(const KTextEditor::Cursor &cursor); virtual void setTextSelection(const KTextEditor::Range &range); //Overridden from Sublime::Document virtual bool closeDocument(bool silent); + virtual bool askForCloseFeedback(); protected: /** Gives us access to the KParts */ QMap partForView() const; /** Lets us override the createViewWidget method safely */ void addPartForView(QWidget* widget, KParts::Part* part); private: class PartDocumentPrivate * const d; }; } #endif diff --git a/shell/workingsetcontroller.cpp b/shell/workingsetcontroller.cpp index dabc35e4c9..3b4f90c002 100644 --- a/shell/workingsetcontroller.cpp +++ b/shell/workingsetcontroller.cpp @@ -1,330 +1,329 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 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 "workingsetcontroller.h" #include #include #include "mainwindow.h" #include "partdocument.h" #include "uicontroller.h" #include #include #include #include #include #include "workingsets/workingset.h" #include "workingsets/workingsettooltipwidget.h" #include "workingsets/workingsetwidget.h" #include "workingsets/closedworkingsetswidget.h" using namespace KDevelop; const int toolTipTimeout = 2000; //Random set of icons that are well distinguishable from each other. If the user doesn't have them, they won't be used. QStringList setIcons = QStringList() << "chronometer" << "games-config-tiles" << "im-user" << "irc-voice" << "irc-operator" << "office-chart-pie" << "office-chart-ring" << "speaker" << "view-pim-notes" << "esd" << "akonadi" << "kleopatra" << "nepomuk" << "package_edutainment_art" << "package_games_amusement" << "package_games_sports" << "package_network" << "package_office_database" << "package_system_applet" << "package_system_emulator" << "preferences-desktop-notification-bell" << "wine" << "utilities-desktop-extra" << "step" << "preferences-web-browser-cookies" << "preferences-plugin" << "preferences-kcalc-constants" << "preferences-desktop-icons" << "tagua" << "inkscape" << "java" << "kblogger" << "preferences-desktop-personal" << "emblem-favorite" << "face-smile-big" << "face-embarrassed" << "user-identity" << "mail-tagged" << "media-playlist-suffle" << "weather-clouds"; WorkingSetController::WorkingSetController(Core* core) : m_emptyWorkingSet(0), m_core(core), m_changingWorkingSet(false) { m_hideToolTipTimer = new QTimer(this); m_hideToolTipTimer->setInterval(toolTipTimeout); m_hideToolTipTimer->setSingleShot(true); } void WorkingSetController::initialize() { //Load all working-sets KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); foreach(const QString& set, setConfig.groupList()) { if(setConfig.group(set).hasKey("iconName")) getWorkingSet(set, setConfig.group(set).readEntry("iconName", QString())); else kDebug() << "have garbage working set with id " << set; } m_emptyWorkingSet = new WorkingSet("empty", "invalid"); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void WorkingSetController::cleanup() { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { foreach (Sublime::Area *area, window->areas()) { if (!area->workingSet().isEmpty()) { Q_ASSERT(m_workingSets.contains(area->workingSet())); m_workingSets[area->workingSet()]->saveFromArea(area, area->rootIndex()); } } } foreach(WorkingSet* set, m_workingSets) { kDebug() << "set" << set->id() << "persistent" << set->isPersistent() << "has areas:" << set->hasConnectedAreas() << "files" << set->fileList(); if(!set->isPersistent() && !set->hasConnectedAreas()) { kDebug() << "deleting"; set->deleteSet(true, true); } delete set; } m_workingSets.clear(); delete m_emptyWorkingSet; m_emptyWorkingSet = 0; } bool WorkingSetController::usingIcon(const QString& icon) { foreach(WorkingSet* set, m_workingSets) if(set->iconName() == icon) return true; return false; } bool WorkingSetController::iconValid(const QString& icon) { return !KIconLoader::global()->iconPath(icon, KIconLoader::Small, true).isNull(); } WorkingSet* WorkingSetController::newWorkingSet(const QString& prefix) { QString newId = QString("%1_%2").arg(prefix).arg(qrand() % 10000000); return getWorkingSet(newId); } WorkingSet* WorkingSetController::getWorkingSet(const QString& id, const QString& _icon) { if(id.isEmpty()) return m_emptyWorkingSet; if(!m_workingSets.contains(id)) { QString icon = _icon; if(icon.isEmpty()) { for(int a = 0; a < 100; ++a) { int pick = (qHash(id) + a) % setIcons.size(); ///@todo Pick icons semantically, by content, and store them in the config if(!usingIcon(setIcons[pick])) { if(iconValid(setIcons[pick])) { icon = setIcons[pick]; break; } } } } if(icon.isEmpty()) { kDebug() << "found no icon for working-set" << id; icon = "invalid"; } WorkingSet* set = new WorkingSet(id, icon); connect(set, SIGNAL(aboutToRemove(WorkingSet*)), this, SIGNAL(aboutToRemoveWorkingSet(WorkingSet*))); m_workingSets[id] = set; emit workingSetAdded(set); } return m_workingSets[id]; } QWidget* WorkingSetController::createSetManagerWidget(MainWindow* parent, Sublime::Area* fixedArea) { if (fixedArea) { return new WorkingSetWidget(parent, fixedArea); } else { return new ClosedWorkingSetsWidget(parent); } } void WorkingSetController::setupActions() { /* KActionCollection * ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); KAction *action; action = ac->addAction ( "view_next_window" ); action->setText( i18n( "Next Document" ) ); action->setIcon( KIcon("go-next") ); action->setShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setWhatsThis( i18n( "Switch the focus to the next open document." ) ); action->setStatusTip( i18n( "Switch the focus to the next open document." ) ); connect( action, SIGNAL(triggered()), this, SLOT(nextDocument()) ); action = ac->addAction ( "view_previous_window" ); action->setText( i18n( "Previous Document" ) ); action->setIcon( KIcon("go-previous") ); action->setShortcut( Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setWhatsThis( i18n( "Switch the focus to the previous open document." ) ); action->setStatusTip( i18n( "Switch the focus to the previous open document." ) ); connect( action, SIGNAL(triggered()), this, SLOT(previousDocument()) ); */ } ActiveToolTip* WorkingSetController::tooltip() const { return m_tooltip; } void WorkingSetController::showToolTip(WorkingSet* set, const QPoint& pos) { delete m_tooltip; KDevelop::MainWindow* window = static_cast(Core::self()->uiControllerInternal()->activeMainWindow()); m_tooltip = new KDevelop::ActiveToolTip(window, pos); QVBoxLayout* layout = new QVBoxLayout(m_tooltip); layout->setMargin(0); WorkingSetToolTipWidget* widget = new WorkingSetToolTipWidget(m_tooltip, set, window); layout->addWidget(widget); m_tooltip->resize( m_tooltip->sizeHint() ); connect(widget, SIGNAL(shouldClose()), m_tooltip, SLOT(close())); ActiveToolTip::showToolTip(m_tooltip); } void WorkingSetController::showGlobalToolTip() { KDevelop::MainWindow* window = static_cast(Core::self()->uiControllerInternal()->activeMainWindow()); showToolTip(getWorkingSet(window->area()->workingSet()), window->mapToGlobal(window->geometry().topRight())); connect(m_hideToolTipTimer, SIGNAL(timeout()), m_tooltip, SLOT(deleteLater())); m_hideToolTipTimer->start(); connect(m_tooltip, SIGNAL(mouseIn()), m_hideToolTipTimer, SLOT(stop())); connect(m_tooltip, SIGNAL(mouseOut()), m_hideToolTipTimer, SLOT(start())); } void WorkingSetController::nextDocument() { if(!m_tooltip) showGlobalToolTip(); m_hideToolTipTimer->stop(); m_hideToolTipTimer->start(toolTipTimeout); if(m_tooltip) { WorkingSetToolTipWidget* widget = m_tooltip->findChild(); Q_ASSERT(widget); widget->nextDocument(); } } void WorkingSetController::previousDocument() { if(!m_tooltip) showGlobalToolTip(); m_hideToolTipTimer->stop(); m_hideToolTipTimer->start(toolTipTimeout); if(m_tooltip) { WorkingSetToolTipWidget* widget = m_tooltip->findChild(); Q_ASSERT(widget); widget->previousDocument(); } } void WorkingSetController::initializeController( UiController* controller ) { connect( controller, SIGNAL(areaCreated(Sublime::Area*)), this, SLOT(areaCreated(Sublime::Area*)) ); } QList< WorkingSet* > WorkingSetController::allWorkingSets() const { return m_workingSets.values(); } void WorkingSetController::areaCreated( Sublime::Area* area ) { if (!area->workingSet().isEmpty()) { WorkingSet* set = getWorkingSet( area->workingSet() ); set->connectArea( area ); } connect(area, SIGNAL(changingWorkingSet(Sublime::Area*,QString,QString)), this, SLOT(changingWorkingSet(Sublime::Area*,QString,QString))); connect(area, SIGNAL(changedWorkingSet(Sublime::Area*,QString,QString)), this, SLOT(changedWorkingSet(Sublime::Area*,QString,QString))); connect(area, SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(viewAdded(Sublime::AreaIndex*,Sublime::View*))); } void WorkingSetController::changingWorkingSet(Sublime::Area* area, const QString& from, const QString& to) { kDebug() << "changing working-set from" << from << "to" << to << "area" << area; if (from == to) return; if (!from.isEmpty()) { WorkingSet* oldSet = getWorkingSet(from); oldSet->disconnectArea(area); if (!oldSet->id().isEmpty()) { oldSet->saveFromArea(area, area->rootIndex()); } } } void WorkingSetController::changedWorkingSet(Sublime::Area* area, const QString& from, const QString& to) { kDebug() << "changed working-set from" << from << "to" << to << "area" << area; if (from == to || m_changingWorkingSet) return; - - // We have to always clear the target area first, because else we cannot perform the switch safely - // (sublime doesn't accept all kinds of changes to the area structure) - area->clearViews(true); if (!to.isEmpty()) { WorkingSet* newSet = getWorkingSet(to); newSet->connectArea(area); - newSet->loadToArea(area, area->rootIndex(), !from.isEmpty()); + newSet->loadToArea(area, area->rootIndex()); + }else{ + // Clear silently, any user-interaction should have happened before + area->clearViews(true); } emit workingSetSwitched(); } void WorkingSetController::viewAdded( Sublime::AreaIndex* , Sublime::View* ) { Sublime::Area* area = qobject_cast< Sublime::Area* >(sender()); Q_ASSERT(area); if (area->workingSet().isEmpty()) { //Spawn a new working-set m_changingWorkingSet = true; WorkingSet* set = Core::self()->workingSetControllerInternal()->newWorkingSet(area->objectName()); kDebug() << "Spawned new working-set" << set->id() << "because a view was added"; set->connectArea(area); set->saveFromArea(area, area->rootIndex()); area->setWorkingSet(set->id()); m_changingWorkingSet = false; } } #include "workingsetcontroller.moc" diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index 19f50761a5..9be207ede2 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,531 +1,516 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 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 "workingset.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; WorkingSet::WorkingSet(QString id, QString icon) : m_id(id), m_iconName(icon) { //Give the working-set icons one color, so they are less disruptive QImage imgActive(KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16).toImage()); QImage imgInactive = imgActive; QColor activeIconColor = QApplication::palette().color(QPalette::Active, QPalette::Highlight); QColor inActiveIconColor = QApplication::palette().color(QPalette::Active, QPalette::Base); KIconEffect::colorize(imgActive, KColorUtils::mix(inActiveIconColor, activeIconColor, 0.7), 0.5); KIconEffect::colorize(imgInactive, KColorUtils::mix(inActiveIconColor, activeIconColor, 0.3), 0.5); m_activeIcon = QIcon(QPixmap::fromImage(imgActive)); m_inactiveIcon = QIcon(QPixmap::fromImage(imgActive)); QImage imgNonPersistent = imgInactive; KIconEffect::deSaturate(imgNonPersistent, 1.0); m_inactiveNonPersistentIcon = QIcon(QPixmap::fromImage(imgNonPersistent)); //effect.apply(KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16), KIconLoader::NoGroup, ); } WorkingSet::WorkingSet( const KDevelop::WorkingSet& rhs ) : QObject() { m_id = rhs.m_id + "_copy_"; } void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplitted()) { setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { setGroup.writeEntry("View Count", area->viewCount()); areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { //The working set config gets an updated list of files QString docSpec = view->document()->documentSpecifier(); setGroup.writeEntry(QString("View %1").arg(index), docSpec); setGroup.writeEntry(QString("View %1 Type").arg(index), view->document()->documentType()); //The area specific config stores the working set documents in order along with their state areaGroup.writeEntry(QString("View %1").arg(index), docSpec); areaGroup.writeEntry(QString("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(wasUpdatesEnabled.contains(window)); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains("0")) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains("1")) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString type = group.readEntry(QString("View %1 Type").arg(i), ""); QString specifier = group.readEntry(QString("View %1").arg(i), ""); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } -void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, bool clear) { +void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { PushValue enableLoading(m_loading, true); DisableMainWindowUpdatesFromArea updatesDisabler(area); kDebug() << "loading working-set" << m_id << "into area" << area; - if(clear) { - kDebug() << "clearing area with working-set" << area->workingSet(); - - // We have to close all views, else we may get serious UI - // consistency problems when the documents intersect. - // Clear the views silently, because the user should be batch-asked - // before changing working sets. - QSet< QString > files = fileList().toSet(); - foreach(Sublime::View* view, area->views()) { - Sublime::UrlDocument* doc = dynamic_cast(view->document()); - if(!doc || !files.contains(doc->documentSpecifier())) - area->closeView(view); - } - } + + QMultiMap recycle; + + foreach( Sublime::View* view, area->views() ) + recycle.insert( view->document()->documentSpecifier(), area->removeView(view) ); + + kDebug() << "recycling" << recycle.size() << "old views"; + + Q_ASSERT( area->views().empty() ); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); - loadToArea(area, areaIndex, setGroup, areaGroup); + loadToArea(area, areaIndex, setGroup, areaGroup, recycle); //activate view in the working set + /// @todo correctly select one out of multiple equal views QString activeView = areaGroup.readEntry("Active View", QString()); foreach (Sublime::View *v, area->views()) { if (v->document()->documentSpecifier() == activeView) { area->setActiveView(v); break; } } + + // Delete views which were not recycled + kDebug() << "deleting " << recycle.size() << " old views"; + qDeleteAll( recycle.values() ); } -void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup) +void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle) { + Q_ASSERT( !areaIndex->isSplitted() ); if (setGroup.hasKey("Orientation")) { QStringList subgroups = setGroup.groupList(); + /// @todo also save and restore the ratio if (subgroups.contains("0") && subgroups.contains("1")) { // kDebug() << "has zero, split:" << split; Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == "Vertical" ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplitted()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } - loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); + loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0"), recycle); - loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); + loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1"), recycle); } } else { - while (areaIndex->isSplitted()) { - Q_ASSERT(areaIndex->first()); - areaIndex->unsplit(areaIndex->second()); - } - - //Track all documents in this areaIndex by their documentSpecifier - QHash viewsBySpec; - foreach (Sublime::View* view, areaIndex->views()) { - viewsBySpec.insert(view->document()->documentSpecifier(), view); - } + //Load all documents from the workingset into this areaIndex int viewCount = setGroup.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString type = setGroup.readEntry(QString("View %1 Type").arg(i), ""); QString specifier = setGroup.readEntry(QString("View %1").arg(i), ""); + Sublime::View* previousView = area->views().empty() ? 0 : area->views().back(); - if (viewsBySpec.contains(specifier)) { - kDebug() << "View already exists!"; + QMultiMap::iterator it = recycle.find( specifier ); + if( it != recycle.end() ) + { + area->addView( *it, areaIndex, previousView ); + recycle.erase( it ); continue; } - + IDocument* doc = Core::self()->documentControllerInternal()->openDocument(specifier, KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { Sublime::View* view = document->createView(); - area->addView(view, areaIndex); - viewsBySpec.insert(specifier, view); + area->addView(view, areaIndex, previousView); } else { kWarning() << "Unable to create view of type " << type; } } - //Now use the workingset's area config (if present) to reorder the documents and load their state - Sublime::View *lastView = 0; - viewCount = areaGroup.readEntry("View Count", 0); + + //Load state for (int i = 0; i < viewCount; ++i) { - QString specifier = areaGroup.readEntry(QString("View %1").arg(i)); - if (!viewsBySpec.contains(specifier)) - continue; - Sublime::View *view = viewsBySpec[specifier]; - - if (lastView) - area->addView(area->removeView(view), areaIndex, lastView); - QString state = areaGroup.readEntry(QString("View %1 State").arg(i)); if (state.length()) - view->setState(state); - - lastView = view; + areaIndex->views()[i]->setState(state); } } } void deleteGroupRecursive(KConfigGroup group) { // kDebug() << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); #ifdef SYNC_OFTEN group.sync(); #endif } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { kDebug() << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); deleteGroupRecursive(setGroup); setGroup.writeEntry("iconName", m_iconName); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); QString lastActiveView = areaGroup.readEntry("Active View", ""); deleteGroupRecursive(areaGroup); if (area->activeView() && area->activeView()->document()) areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); else areaGroup.writeEntry("Active View", lastActiveView); saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) { deleteGroupRecursive(setGroup); deleteGroupRecursive(areaGroup); } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); kDebug() << "added view in" << area << ", id" << m_id; if (m_loading) { kDebug() << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); kDebug() << "removed view in" << area << ", id" << m_id; if (m_loading) { kDebug() << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif kDebug() << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::inactiveIcon() const { if(isPersistent()) return m_inactiveIcon; else return m_inactiveNonPersistentIcon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } WorkingSet* WorkingSet::clone() { WorkingSet* ret = new WorkingSet( *this ); return ret; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { kDebug() << "tried to double-connect area"; return; } kDebug() << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(areaViewAdded(Sublime::AreaIndex*,Sublime::View*)) ); connect( area, SIGNAL(viewRemoved(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(areaViewRemoved(Sublime::AreaIndex*,Sublime::View*)) ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { kDebug() << "tried to disconnect not connected area"; return; } kDebug() << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(areaViewAdded(Sublime::AreaIndex*,Sublime::View*)) ); disconnect( area, SIGNAL(viewRemoved(Sublime::AreaIndex*,Sublime::View*)), this, SLOT(areaViewRemoved(Sublime::AreaIndex*,Sublime::View*)) ); m_areas.removeAll( area ); } void WorkingSet::deleteSet() { deleteSet( false ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); kDebug() << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } QIcon WorkingSet::activeIcon() const { return m_activeIcon; } QString WorkingSet::iconName() const { return m_iconName; } #include "workingset.moc" diff --git a/shell/workingsets/workingset.h b/shell/workingsets/workingset.h index 7c5179754c..ae2aa94f9b 100644 --- a/shell/workingsets/workingset.h +++ b/shell/workingsets/workingset.h @@ -1,106 +1,105 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU 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 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 WORKINGSET_H #define WORKINGSET_H #include #include #include #include namespace Sublime { class Area; class AreaIndex; class View; } namespace KDevelop { class WorkingSet : public QObject { Q_OBJECT public: WorkingSet(QString id, QString icon); bool isConnected(Sublime::Area* area); QString iconName() const; QIcon activeIcon() const; QIcon inactiveIcon() const; bool isPersistent() const; void setPersistent(bool persistent); QString id() const; ///Creates a copy of this working-set with a new identity WorkingSet* clone(); QStringList fileList() const; bool isEmpty() const; ///Updates this working-set from the given area and area-index void saveFromArea(Sublime::Area* area, Sublime::AreaIndex * areaIndex); ///Loads this working-set directly from the configuration file, and stores it in the given area ///Does not ask the user, this should be done beforehand. - ///@param clear If this is true, the area will be cleared before - void loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, bool clear = true); + void loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex); bool hasConnectedAreas() const; bool hasConnectedAreas(QList areas) const; void connectArea(Sublime::Area* area); void disconnectArea(Sublime::Area* area); void deleteSet(bool force, bool silent = false); private slots: void deleteSet(); void areaViewAdded(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); void areaViewRemoved(Sublime::AreaIndex* /*index*/, Sublime::View* /*view*/); signals: void setChangedSignificantly(); void aboutToRemove(WorkingSet*); private: void changed(Sublime::Area* area); void saveFromArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup); - void loadToArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup); + void loadToArea(Sublime::Area* area, Sublime::AreaIndex *areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle); WorkingSet(const WorkingSet& rhs); QString m_id; QString m_iconName; QIcon m_activeIcon, m_inactiveIcon, m_inactiveNonPersistentIcon; QList > m_areas; static bool m_loading; }; } #endif // WORKINGSET_H diff --git a/sublime/area.cpp b/sublime/area.cpp index 76b21ad98b..df29ce359f 100644 --- a/sublime/area.cpp +++ b/sublime/area.cpp @@ -1,479 +1,475 @@ /*************************************************************************** * 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 "area.h" #include #include #include #include #include "view.h" #include "document.h" #include "areaindex.h" #include "controller.h" namespace Sublime { // struct AreaPrivate struct AreaPrivate { AreaPrivate() { rootIndex = new RootAreaIndex(); currentIndex = rootIndex; controller = 0; } AreaPrivate(const AreaPrivate &p) { rootIndex = new RootAreaIndex(*(p.rootIndex)); currentIndex = rootIndex; controller = p.controller; toolViewPositions.clear(); desiredToolViews = p.desiredToolViews; shownToolViews = p.shownToolViews; workingSet = p.workingSet; title = p.title; iconName = p.iconName; } ~AreaPrivate() { delete rootIndex; } struct ViewFinder { ViewFinder(View *_view): view(_view), index(0) {} Area::WalkerMode operator() (AreaIndex *idx) { if (idx->hasView(view)) { index = idx; return Area::StopWalker; } return Area::ContinueWalker; } View *view; AreaIndex *index; }; struct ViewLister { Area::WalkerMode operator()(AreaIndex *idx) { views += idx->views(); return Area::ContinueWalker; } QList views; }; QString title; RootAreaIndex *rootIndex; AreaIndex *currentIndex; Controller *controller; QList toolViews; QMap toolViewPositions; QMap desiredToolViews; QMap shownToolViews; QMap thickness; QString iconName; QString workingSet; QWeakPointer activeView; }; // class Area Area::Area(Controller *controller, const QString &name, const QString &title) :QObject(controller), d( new AreaPrivate() ) { // FIXME: using objectName seems fishy. Introduce areaType method, // or some such. setObjectName(name); d->title = title; d->controller = controller; d->iconName = "kdevelop"; d->workingSet.clear(); kDebug() << "initial working-set:" << d->workingSet; initialize(); } Area::Area(const Area &area) : QObject(area.controller()), d( new AreaPrivate( *(area.d) ) ) { setObjectName(area.objectName()); //clone toolviews d->toolViews.clear(); foreach (View *view, area.toolViews()) addToolView(view->document()->createView(), area.toolViewPosition(view)); initialize(); } void Area::initialize() { connect(this, SIGNAL(viewAdded(Sublime::AreaIndex*,Sublime::View*)), d->controller, SLOT(notifyViewAdded(Sublime::AreaIndex*,Sublime::View*))); connect(this, SIGNAL(aboutToRemoveView(Sublime::AreaIndex*,Sublime::View*)), d->controller, SLOT(notifyViewRemoved(Sublime::AreaIndex*,Sublime::View*))); connect(this, SIGNAL(toolViewAdded(Sublime::View*,Sublime::Position)), d->controller, SLOT(notifyToolViewAdded(Sublime::View*,Sublime::Position))); connect(this, SIGNAL(aboutToRemoveToolView(Sublime::View*,Sublime::Position)), d->controller, SLOT(notifyToolViewRemoved(Sublime::View*,Sublime::Position))); connect(this, SIGNAL(toolViewMoved(Sublime::View*,Sublime::Position)), d->controller, SIGNAL(toolViewMoved(Sublime::View*))); /* In theory, ownership is passed to us, so should not bother detecting deletion outside. */ connect(this, SIGNAL(destroyed(QObject*)), d->controller, SLOT(removeArea(QObject*))); } Area::~Area() { delete d; } View* Area::activeView() { return d->activeView.data(); } void Area::setActiveView(View* view) { d->activeView = view; } void Area::addView(View *view, AreaIndex *index, View *after) { //View *after = 0; if (!after && controller()->openAfterCurrent()) { after = activeView(); } index->add(view, after); connect(view, SIGNAL(positionChanged(Sublime::View*,int)), this, SLOT(positionChanged(Sublime::View*,int))); kDebug() << "view added in" << this; connect(this, SIGNAL(destroyed()), view, SLOT(deleteLater())); emit viewAdded(index, view); } void Area::addView(View *view, View *after) { AreaIndex *index = d->currentIndex; if (after) { AreaIndex *i = indexOf(after); if (i) index = i; } addView(view, index); } void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation) { AreaIndex *indexToSplit = indexOf(viewToSplit); addView(view, indexToSplit, orientation); } void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation) { indexToSplit->split(view, orientation); emit viewAdded(indexToSplit, view); connect(this, SIGNAL(destroyed()), view, SLOT(deleteLater())); } View* Area::removeView(View *view) { AreaIndex *index = indexOf(view); Q_ASSERT(index); emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); return view; } AreaIndex *Area::indexOf(View *view) { AreaPrivate::ViewFinder f(view); walkViews(f, d->rootIndex); return f.index; } RootAreaIndex *Area::rootIndex() const { return d->rootIndex; } void Area::addToolView(View *view, Position defaultPosition) { d->toolViews.append(view); QString id = view->document()->documentSpecifier(); Position position = defaultPosition; if (d->desiredToolViews.contains(id)) position = d->desiredToolViews[id]; d->desiredToolViews[id] = position; d->toolViewPositions[view] = position; emit toolViewAdded(view, position); } void Sublime::Area::raiseToolView(View * toolView) { emit requestToolViewRaise(toolView); } View* Area::removeToolView(View *view) { if (!d->toolViews.contains(view)) return 0; emit aboutToRemoveToolView(view, d->toolViewPositions[view]); QString id = view->document()->documentSpecifier(); kDebug() << this << "removed tool view " << id; d->desiredToolViews.remove(id); d->toolViews.removeAll(view); d->toolViewPositions.remove(view); return view; } void Area::moveToolView(View *toolView, Position newPosition) { if (!d->toolViews.contains(toolView)) return; QString id = toolView->document()->documentSpecifier(); d->desiredToolViews[id] = newPosition; d->toolViewPositions[toolView] = newPosition; emit toolViewMoved(toolView, newPosition); } QList &Area::toolViews() const { return d->toolViews; } Position Area::toolViewPosition(View *toolView) const { return d->toolViewPositions[toolView]; } Controller *Area::controller() const { return d->controller; } QList Sublime::Area::views() { AreaPrivate::ViewLister lister; walkViews(lister, d->rootIndex); return lister.views; } QString Area::title() const { return d->title; } void Area::setTitle(const QString &title) { d->title = title; } void Area::save(KConfigGroup& group) const { QStringList desired; QMap::iterator i, e; for (i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) { desired << i.key() + ':' + QString::number(static_cast(i.value())); } group.writeEntry("desired views", desired); kDebug() << "save " << this << "wrote" << group.readEntry("desired views", ""); group.writeEntry("view on left", shownToolViews(Sublime::Left)); group.writeEntry("view on right", shownToolViews(Sublime::Right)); group.writeEntry("view on top", shownToolViews(Sublime::Top)); group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom)); group.writeEntry("thickness left", thickness(Sublime::Left)); group.writeEntry("thickness right", thickness(Sublime::Right)); group.writeEntry("thickness bottom", thickness(Sublime::Bottom)); group.writeEntry("thickness top", thickness(Sublime::Top)); group.writeEntry("working set", d->workingSet); } void Area::load(const KConfigGroup& group) { kDebug() << "loading areas config"; d->desiredToolViews.clear(); QStringList desired = group.readEntry("desired views", QStringList()); foreach (const QString &s, desired) { int i = s.indexOf(':'); if (i != -1) { QString id = s.left(i); int pos_i = s.mid(i+1).toInt(); Sublime::Position pos = static_cast(pos_i); if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom) pos = Sublime::Bottom; d->desiredToolViews[id] = pos; } } setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList())); setShownToolViews(Sublime::Right, group.readEntry("view on right", QStringList())); setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList())); setShownToolViews(Sublime::Bottom, group.readEntry("view on bottom", QStringList())); setThickness(Sublime::Left, group.readEntry("thickness left", -1)); setThickness(Sublime::Right, group.readEntry("thickness right", -1)); setThickness(Sublime::Bottom, group.readEntry("thickness bottom", -1)); setThickness(Sublime::Top, group.readEntry("thickness top", -1)); setWorkingSet(group.readEntry("working set", d->workingSet)); } bool Area::wantToolView(const QString& id) { kDebug() << d->desiredToolViews << " " << id; return (d->desiredToolViews.contains(id)); } void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids) { d->shownToolViews[pos] = ids; } QStringList Area::shownToolViews(Sublime::Position pos) const { return d->shownToolViews[pos]; } void Area::setDesiredToolViews( const QMap& desiredToolViews) { d->desiredToolViews = desiredToolViews; } void Area::setThickness(Sublime::Position pos, int thickness) { d->thickness[pos] = thickness; } int Area::thickness(Sublime::Position pos) const { if (!d->thickness.count(pos)) return -1; return (d->thickness)[pos]; } QString Area::iconName() const { return d->iconName; } void Area::setIconName(const QString& iconName) { d->iconName = iconName; } void Area::positionChanged(View *view, int newPos) { kDebug() << view << newPos; AreaIndex *index = indexOf(view); index->views().move(index->views().indexOf(view), newPos); } QString Area::workingSet() const { return d->workingSet; } void Area::setWorkingSet(QString name) { if(name != d->workingSet) { kDebug() << this << "setting new working-set" << name; QString oldName = d->workingSet; emit changingWorkingSet(this, oldName, name); d->workingSet = name; emit changedWorkingSet(this, oldName, name); } } bool Area::closeView(View* view, bool silent) { - static QSet alreadyClosingViews; - - if(alreadyClosingViews.contains(view)) - return false; // The view is already being closed, so ignore the closeView request - QWeakPointer doc = view->document(); // We don't just delete the view, because if silent is false, we might need to ask the user. - if(doc) + if(doc && !silent) { + // Do some counting to check whether we need to ask the user for feedback kDebug() << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this; int viewsInCurrentArea = 0; // Number of views for the same document in the current area int viewsInOtherAreas = 0; // Number of views for the same document in other areas int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set foreach(View* otherView, doc.data()->views()) { Area* area = controller()->areaForView(otherView); if(area == this) viewsInCurrentArea += 1; if(!area || (area != this)) viewsInOtherAreas += 1; - if(!area || (area != this && area->workingSet() != workingSet())) + if(area && area != this && area->workingSet() != workingSet()) viewsInOtherWorkingSets += 1; } if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0)) { - alreadyClosingViews = doc.data()->views().toSet(); - bool ret = doc.data()->closeDocument(silent); - alreadyClosingViews.clear(); - return ret; + // Time to ask the user for feedback, because the document will be completely closed + // due to working-set synchronization + if( !doc.data()->askForCloseFeedback() ) + return false; } } // otherwise we can silently close the view, // the document will still have an opened view somewhere AreaIndex *index = indexOf(view); Q_ASSERT(index); emit aboutToRemoveView(index, view); index->remove(view); emit viewRemoved(index, view); delete view; return true; } void Area::clearViews(bool silent) { foreach(Sublime::View* view, views()) closeView(view, silent); } } #include "area.moc" diff --git a/sublime/document.cpp b/sublime/document.cpp index dab62289fe..ee7bd67ee8 100644 --- a/sublime/document.cpp +++ b/sublime/document.cpp @@ -1,149 +1,163 @@ /*************************************************************************** * 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 "document.h" #include #include "view.h" #include "area.h" #include "controller.h" namespace Sublime { // struct DocumentPrivate struct DocumentPrivate { DocumentPrivate(Document *doc): m_document(doc) {} void removeView(QObject *obj) { views.removeAll(reinterpret_cast(obj)); emit m_document->viewNumberChanged(m_document); //no need to keep empty document - we need to remove it if (views.count() == 0) { emit m_document->aboutToDelete(m_document); m_document->deleteLater(); } } Controller *controller; QList views; QIcon statusIcon; QString documentToolTip; Document *m_document; }; //class Document Document::Document(const QString &title, Controller *controller) :QObject(controller), d( new DocumentPrivate(this) ) { setObjectName(title); d->controller = controller; d->controller->addDocument(this); connect(this, SIGNAL(destroyed(QObject*)), d->controller, SLOT(removeDocument(QObject*))); } Document::~Document() { delete d; } Controller *Document::controller() const { return d->controller; } View *Document::createView() { View *view = newView(this); connect(view, SIGNAL(destroyed(QObject*)), this, SLOT(removeView(QObject*))); d->views.append(view); return view; } const QList &Document::views() const { return d->views; } QString Document::title() const { return objectName(); } QString Document::toolTip() const { return d->documentToolTip; } void Document::setTitle(const QString& newTitle) { setObjectName(newTitle); emit titleChanged(this); } void Document::setToolTip(const QString& newToolTip) { d->documentToolTip=newToolTip; } View *Document::newView(Document *doc) { //first create View, second emit the signal View *newView = new View(doc); emit viewNumberChanged(this); return newView; } void Document::setStatusIcon(QIcon icon) { d->statusIcon = icon; emit statusIconChanged(this); } QIcon Document::statusIcon() const { return d->statusIcon; } -bool Document::closeDocument(bool silent) +void Document::closeViews() { + kDebug() << "closing all views for the document"; foreach (Sublime::Area *area, controller()->allAreas()) { QList areaViews = area->views(); foreach (Sublime::View *view, areaViews) { if (views().contains(view)) { area->removeView(view); delete view; } } } + Q_ASSERT(views().isEmpty()); +} + +bool Document::askForCloseFeedback() +{ + return true; +} + +bool Document::closeDocument(bool silent) +{ + if( !silent && !askForCloseFeedback() ) + return false; + closeViews(); deleteLater(); return true; } } #include "document.moc" diff --git a/sublime/document.h b/sublime/document.h index 844727ecf4..780590c373 100644 --- a/sublime/document.h +++ b/sublime/document.h @@ -1,121 +1,134 @@ /*************************************************************************** * 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 SUBLIMEDOCUMENT_H #define SUBLIMEDOCUMENT_H #include #include #include "sublimeexport.h" class QIcon; class QWidget; namespace Sublime { class Area; class View; class Controller; /** @short Abstract base class for all Sublime documents Subclass from Document and implement createViewWidget() method to return a new widget for a view. */ class SUBLIME_EXPORT Document: public QObject { Q_OBJECT public: /**Creates a document and adds it to a @p controller.*/ Document(const QString &title, Controller *controller); ~Document(); /**@return the new view for this document. @note it will not create a widget, just return a view object.*/ View *createView(); /**@return the list of all views in all areas for this document.*/ const QList &views() const; /**@return the controller for this document.*/ Controller *controller() const; /**@return the document title.*/ QString title() const; /**Set the document title.*/ void setTitle(const QString& newTitle); void setToolTip(const QString& newToolTip); QString toolTip() const; /**@return the type of document which can be written to config.*/ virtual QString documentType() const = 0; /**@return the specifics of this document which can be written to config.*/ virtual QString documentSpecifier() const = 0; + /** + * If the document is in a state where data may be lost while closking, + * asks the user whether he really wants to close the document. + * + * This function may also take actions like saving the document before closing + * if the user desires so. + * + * @return true if the document is allowed to be closed, otherwise false. + * + * The default implementation always returns true. + * + * */ + virtual bool askForCloseFeedback(); + /**Should try closing the document, eventually asking the user for feedback. * *If closing is successful, all views should be deleted, and the document itself *be scheduled for deletion using deleteLater(). * - *The default implementation will close all views and then deletes the document itself. - *Override this if you want to confirm closing with the user. - * * @param silent If this is true, the user must not be asked. * * Returns whether closing was successful (The user did not push 'Cancel') */ virtual bool closeDocument(bool silent = false); void setStatusIcon(QIcon icon); QIcon statusIcon() const; Q_SIGNALS: /**Emitted when the view is added or deleted. Use Document::views to find out which views and how many of them are still there.*/ void viewNumberChanged(Sublime::Document *doc); /**Emitted when the document is about to be deleted but is still in valid state.*/ void aboutToDelete(Sublime::Document *doc); /**Emitted when the document's title is changed.*/ void titleChanged(Sublime::Document *doc); /**Emitted when the document status-icon has changed */ void statusIconChanged(Sublime::Document *doc); protected: /**Creates and returns the new view. Reimplement in subclasses to instantiate views of derived from Sublime::View classes.*/ virtual View *newView(Document *doc); /**Reimplement this to create and return the new widget to display this document in the view. This method is used by View class when it is asked for its widget.*/ virtual QWidget *createViewWidget(QWidget *parent = 0) = 0; - + /** Closes all views associated to this document */ + virtual void closeViews(); + private: Q_PRIVATE_SLOT(d, void removeView(QObject*)) struct DocumentPrivate *const d; friend struct DocumentPrivate; friend class View; }; } #endif