session.cpp 29.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
hjk's avatar
hjk committed
3 4
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22 23 24 25
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
hjk's avatar
hjk committed
29 30 31

#include "session.h"

con's avatar
con committed
32 33 34 35 36
#include "project.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
#include "nodesvisitor.h"
#include "editorconfiguration.h"
37
#include "projectnodes.h"
con's avatar
con committed
38 39 40

#include <coreplugin/icore.h>
#include <coreplugin/imode.h>
41
#include <coreplugin/documentmanager.h>
con's avatar
con committed
42 43
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
44
#include <coreplugin/coreconstants.h>
45
#include <coreplugin/progressmanager/progressmanager.h>
con's avatar
con committed
46 47 48 49
#include <coreplugin/modemanager.h>

#include <texteditor/itexteditor.h>

hjk's avatar
hjk committed
50 51 52
#include <utils/listutils.h>
#include <utils/qtcassert.h>

53 54 55 56 57 58
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QFuture>
#include <QSettings>
#include <QTimer>
hjk's avatar
hjk committed
59

60 61 62 63
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
#include <QTextCodec>
con's avatar
con committed
64 65 66 67 68

namespace {
    bool debug = false;
}

69
using namespace Core;
Jarek Kobus's avatar
Jarek Kobus committed
70 71
using Utils::PersistentSettingsReader;
using Utils::PersistentSettingsWriter;
72

con's avatar
con committed
73
using namespace ProjectExplorer;
74
using namespace ProjectExplorer::Internal;
con's avatar
con committed
75

76 77 78 79 80 81 82 83 84 85
/*!
     \class ProjectExplorer::SessionManager

     \brief Session management.

     TODO the interface of this class is not really great.
     The implementation suffers that all the functions from the
     public interface just wrap around functions which do the actual work
     This could be improved.
*/
con's avatar
con committed
86

hjk's avatar
hjk committed
87 88
SessionManager::SessionManager(QObject *parent)
  : QObject(parent),
89
    m_sessionNode(new SessionNode(this)),
90
    m_sessionName(QLatin1String("default")),
91
    m_virginSession(true),
92
    m_loadingSession(false),
Tobias Hunger's avatar
Tobias Hunger committed
93 94
    m_startupProject(0),
    m_writer(0)
con's avatar
con committed
95
{
96
    connect(ModeManager::instance(), SIGNAL(currentModeChanged(Core::IMode*)),
con's avatar
con committed
97
            this, SLOT(saveActiveMode(Core::IMode*)));
98

hjk's avatar
hjk committed
99
    EditorManager *em = ICore::editorManager();
100

Robert Loehning's avatar
Robert Loehning committed
101 102 103
    connect(em, SIGNAL(editorCreated(Core::IEditor*,QString)),
            this, SLOT(configureEditor(Core::IEditor*,QString)));
    connect(ProjectExplorerPlugin::instance(), SIGNAL(currentProjectChanged(ProjectExplorer::Project*)),
con's avatar
con committed
104
            this, SLOT(updateWindowTitle()));
105 106 107 108
    connect(em, SIGNAL(editorOpened(Core::IEditor*)),
            this, SLOT(markSessionFileDirty()));
    connect(em, SIGNAL(editorsClosed(QList<Core::IEditor*>)),
            this, SLOT(markSessionFileDirty()));
hjk's avatar
hjk committed
109
}
con's avatar
con committed
110 111 112

SessionManager::~SessionManager()
{
113
    emit aboutToUnloadSession(m_sessionName);
Tobias Hunger's avatar
Tobias Hunger committed
114
    delete m_writer;
con's avatar
con committed
115 116 117 118 119
}


bool SessionManager::isDefaultVirgin() const
{
dt's avatar
dt committed
120 121
    return isDefaultSession(m_sessionName)
            && m_virginSession;
con's avatar
con committed
122 123 124 125
}

bool SessionManager::isDefaultSession(const QString &session) const
{
hjk's avatar
hjk committed
126
    return session == QLatin1String("default");
con's avatar
con committed
127 128 129 130 131
}


void SessionManager::saveActiveMode(Core::IMode *mode)
{
132
    setValue(QLatin1String("ActiveMode"), mode->id().toString());
con's avatar
con committed
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
}

void SessionManager::clearProjectFileCache()
{
    // If triggered by the fileListChanged signal of one project
    // only invalidate cache for this project
    Project *pro = qobject_cast<Project*>(sender());
    if (pro)
        m_projectFileCache.remove(pro);
    else
        m_projectFileCache.clear();
}

bool SessionManager::recursiveDependencyCheck(const QString &newDep, const QString &checkDep) const
{
    if (newDep == checkDep)
        return false;

151
    foreach (const QString &dependency, m_depMap.value(checkDep)) {
con's avatar
con committed
152 153 154 155 156 157 158
        if (!recursiveDependencyCheck(newDep, dependency))
            return false;
    }

    return true;
}

159
/*
160
 * The dependency management exposes an interface based on projects, but
161 162 163 164 165 166 167
 * is internally purely string based. This is suboptimal. Probably it would be
 * nicer to map the filenames to projects on load and only map it back to
 * filenames when saving.
 */

QList<Project *> SessionManager::dependencies(const Project *project) const
{
168
    const QString &proName = project->document()->fileName();
169
    const QStringList &proDeps = m_depMap.value(proName);
170 171 172 173 174 175 176 177 178 179 180

    QList<Project *> projects;
    foreach (const QString &dep, proDeps) {
        if (Project *pro = projectForFile(dep))
            projects += pro;
    }

    return projects;
}

bool SessionManager::hasDependency(const Project *project, const Project *depProject) const
con's avatar
con committed
181
{
182 183
    const QString &proName = project->document()->fileName();
    const QString &depName = depProject->document()->fileName();
con's avatar
con committed
184

185
    const QStringList &proDeps = m_depMap.value(proName);
con's avatar
con committed
186 187 188
    return proDeps.contains(depName);
}

189
bool SessionManager::canAddDependency(const Project *project, const Project *depProject) const
con's avatar
con committed
190
{
191 192
    const QString &newDep = project->document()->fileName();
    const QString &checkDep = depProject->document()->fileName();
con's avatar
con committed
193 194 195 196

    return recursiveDependencyCheck(newDep, checkDep);
}

dt's avatar
dt committed
197
bool SessionManager::addDependency(Project *project, Project *depProject)
con's avatar
con committed
198
{
199 200
    const QString &proName = project->document()->fileName();
    const QString &depName = depProject->document()->fileName();
con's avatar
con committed
201 202 203 204 205

    // check if this dependency is valid
    if (!recursiveDependencyCheck(proName, depName))
        return false;

206
    QStringList proDeps = m_depMap.value(proName);
con's avatar
con committed
207 208
    if (!proDeps.contains(depName)) {
        proDeps.append(depName);
209
        m_depMap[proName] = proDeps;
con's avatar
con committed
210
    }
dt's avatar
dt committed
211
    emit dependencyChanged(project, depProject);
con's avatar
con committed
212 213 214 215

    return true;
}

dt's avatar
dt committed
216
void SessionManager::removeDependency(Project *project, Project *depProject)
217
{
218 219
    const QString &proName = project->document()->fileName();
    const QString &depName = depProject->document()->fileName();
220

221
    QStringList proDeps = m_depMap.value(proName);
222 223
    proDeps.removeAll(depName);
    if (proDeps.isEmpty()) {
224
        m_depMap.remove(proName);
225
    } else {
226
        m_depMap[proName] = proDeps;
227
    }
dt's avatar
dt committed
228
    emit dependencyChanged(project, depProject);
229 230
}

con's avatar
con committed
231 232 233
void SessionManager::setStartupProject(Project *startupProject)
{
    if (debug)
234
        qDebug() << Q_FUNC_INFO << (startupProject ? startupProject->displayName() : QLatin1String("0"));
con's avatar
con committed
235 236

    if (startupProject) {
237
        Q_ASSERT(m_projects.contains(startupProject));
con's avatar
con committed
238 239
    }

240
    if (m_startupProject == startupProject)
241 242
        return;

243
    m_startupProject = startupProject;
con's avatar
con committed
244 245 246 247 248
    emit startupProjectChanged(startupProject);
}

Project *SessionManager::startupProject() const
{
249
    return m_startupProject;
con's avatar
con committed
250 251 252 253 254 255 256 257 258
}

void SessionManager::addProject(Project *project)
{
    addProjects(QList<Project*>() << project);
}

void SessionManager::addProjects(const QList<Project*> &projects)
{
dt's avatar
dt committed
259
    m_virginSession = false;
con's avatar
con committed
260 261
    QList<Project*> clearedList;
    foreach (Project *pro, projects) {
262
        if (!m_projects.contains(pro)) {
con's avatar
con committed
263
            clearedList.append(pro);
264
            m_projects.append(pro);
265
            m_sessionNode->addProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
con's avatar
con committed
266 267 268 269

            connect(pro, SIGNAL(fileListChanged()),
                    this, SLOT(clearProjectFileCache()));

270 271 272
            connect(pro, SIGNAL(displayNameChanged()),
                    this, SLOT(projectDisplayNameChanged()));

con's avatar
con committed
273
            if (debug)
274
                qDebug() << "SessionManager - adding project " << pro->displayName();
con's avatar
con committed
275 276 277 278 279 280 281 282 283 284 285 286 287
        }
    }

    foreach (Project *pro, clearedList) {
        emit projectAdded(pro);
    }

    if (clearedList.count() == 1)
        emit singleProjectAdded(clearedList.first());
}

void SessionManager::removeProject(Project *project)
{
dt's avatar
dt committed
288
    m_virginSession = false;
con's avatar
con committed
289 290 291 292 293 294 295
    if (project == 0) {
        qDebug() << "SessionManager::removeProject(0) ... THIS SHOULD NOT HAPPEN";
        return;
    }
    removeProjects(QList<Project*>() << project);
}

296 297 298 299 300
bool SessionManager::loadingSession()
{
    return m_loadingSession;
}

con's avatar
con committed
301 302 303 304 305 306 307
bool SessionManager::save()
{
    if (debug)
        qDebug() << "SessionManager - saving session" << m_sessionName;

    emit aboutToSaveSession();

Tobias Hunger's avatar
Tobias Hunger committed
308 309 310 311 312
    if (!m_writer || m_writer->fileName() != sessionNameToFileName(m_sessionName)) {
        delete m_writer;
        m_writer = new Utils::PersistentSettingsWriter(sessionNameToFileName(m_sessionName),
                                                       QLatin1String("QtCreatorSession"));
    }
313

314
    QVariantMap data;
315
    // save the startup project
Tobias Hunger's avatar
Tobias Hunger committed
316
    if (m_startupProject)
317
        data.insert(QLatin1String("StartupProject"), m_startupProject->document()->fileName());
318 319

    QStringList projectFiles;
320
    foreach (Project *pro, m_projects)
321
        projectFiles << pro->document()->fileName();
322 323

    // Restore infromation on projects that failed to load:
324 325 326 327
    // don't readd projects to the list, which the user loaded
    foreach (const QString &failed, m_failedProjects)
        if (!projectFiles.contains(failed))
            projectFiles << failed;
328

329
    data.insert(QLatin1String("ProjectList"), projectFiles);
330 331

    QMap<QString, QVariant> depMap;
332 333
    QMap<QString, QStringList>::const_iterator i = m_depMap.constBegin();
    while (i != m_depMap.constEnd()) {
334 335 336 337 338 339 340 341
        QString key = i.key();
        QStringList values;
        foreach (const QString &value, i.value()) {
            values << value;
        }
        depMap.insert(key, values);
        ++i;
    }
342
    data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
con's avatar
con committed
343

344 345 346 347 348 349 350
    int editorCount = 0;
    QList<Core::IEditor *> editors = ICore::editorManager()->openedEditors();
    foreach (Core::IEditor *editor, editors) {
        Q_ASSERT(editor);
        if (!editor->isTemporary())
            ++editorCount;
    }
351 352
    data.insert(QLatin1String("OpenEditors"), editorCount);
    data.insert(QLatin1String("EditorSettings"), ICore::editorManager()->saveState().toBase64());
353 354

    QMap<QString, QVariant>::const_iterator it, end;
355
    end = m_values.constEnd();
356
    QStringList keys;
357
    for (it = m_values.constBegin(); it != end; ++it) {
358
        data.insert(QLatin1String("value-") + it.key(), it.value());
359 360 361
        keys << it.key();
    }

362
    data.insert(QLatin1String("valueKeys"), keys);
363

364
    bool result = m_writer->save(data, Core::ICore::mainWindow());
con's avatar
con committed
365 366
    if (!result) {
        QMessageBox::warning(0, tr("Error while saving session"),
Tobias Hunger's avatar
Tobias Hunger committed
367
                                tr("Could not save session to file %1").arg(m_writer->fileName().toUserOutput()));
con's avatar
con committed
368 369 370 371 372 373 374 375 376
    }

    if (debug)
        qDebug() << "SessionManager - saving session returned " << result;

    return result;
}

/*!
377
  \fn bool SessionManager::closeAllProjects()
con's avatar
con committed
378

379
  Closes all projects
con's avatar
con committed
380
  */
381
void SessionManager::closeAllProjects()
con's avatar
con committed
382
{
383 384
    setStartupProject(0);
    removeProjects(projects());
con's avatar
con committed
385 386
}

387
const QList<Project *> &SessionManager::projects() const
con's avatar
con committed
388
{
389
    return m_projects;
con's avatar
con committed
390 391 392 393 394
}

QStringList SessionManager::dependencies(const QString &proName) const
{
    QStringList result;
395
    foreach (const QString &dep, m_depMap.value(proName))
con's avatar
con committed
396 397 398 399 400 401 402 403 404 405 406 407 408
        result += dependencies(dep);

    result << proName;
    return result;
}

QStringList SessionManager::dependenciesOrder() const
{
    QList<QPair<QString, QStringList> > unordered;
    QStringList ordered;

    // copy the map to a temporary list
    foreach (Project *pro, projects()) {
409
        const QString &proName = pro->document()->fileName();
con's avatar
con committed
410
        unordered << QPair<QString, QStringList>
411
            (proName, m_depMap.value(proName));
con's avatar
con committed
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
    }

    while (!unordered.isEmpty()) {
        for (int i=(unordered.count()-1); i>=0; --i) {
            if (unordered.at(i).second.isEmpty()) {
                ordered << unordered.at(i).first;
                unordered.removeAt(i);
            }
        }

        // remove the handled projects from the dependency lists
        // of the remaining unordered projects
        for (int i = 0; i < unordered.count(); ++i) {
            foreach (const QString &pro, ordered) {
                QStringList depList = unordered.at(i).second;
                depList.removeAll(pro);
                unordered[i].second = depList;
            }
        }
    }

    return ordered;
}

QList<Project *> SessionManager::projectOrder(Project *project) const
{
    QList<Project *> result;

    QStringList pros;
    if (project) {
442
        pros = dependencies(project->document()->fileName());
con's avatar
con committed
443 444 445 446 447 448
    } else {
        pros = dependenciesOrder();
    }

    foreach (const QString &proFile, pros) {
        foreach (Project *pro, projects()) {
449
            if (pro->document()->fileName() == proFile) {
con's avatar
con committed
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
                result << pro;
                break;
            }
        }
    }

    return result;
}

Project *SessionManager::projectForNode(Node *node) const
{
    if (!node)
        return 0;

    Project *project = 0;

    FolderNode *rootProjectNode = qobject_cast<FolderNode*>(node);
    if (!rootProjectNode)
        rootProjectNode = node->parentFolderNode();
    while (rootProjectNode && rootProjectNode->parentFolderNode() != m_sessionNode)
        rootProjectNode = rootProjectNode->parentFolderNode();

dt's avatar
dt committed
472
    Q_ASSERT(rootProjectNode);
con's avatar
con committed
473 474 475 476 477 478 479 480 481 482 483 484

    QList<Project *> projectList = projects();
    foreach (Project *p, projectList) {
        if (p->rootProjectNode() == rootProjectNode) {
            project = p;
            break;
        }
    }

    return project;
}

dt's avatar
dt committed
485
Node *SessionManager::nodeForFile(const QString &fileName, Project *project) const
con's avatar
con committed
486 487
{
    Node *node = 0;
dt's avatar
dt committed
488 489 490
    if (!project)
        project = projectForFile(fileName);
    if (project) {
con's avatar
con committed
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
        FindNodesForFileVisitor findNodes(fileName);
        project->rootProjectNode()->accept(&findNodes);

        foreach (Node *n, findNodes.nodes()) {
            // prefer file nodes
            if (!node || (node->nodeType() != FileNodeType && n->nodeType() == FileNodeType))
                node = n;
        }
    }

    return node;
}

Project *SessionManager::projectForFile(const QString &fileName) const
{
    if (debug)
        qDebug() << "SessionManager::projectForFile(" << fileName << ")";

509
    const QList<Project *> &projectList = projects();
con's avatar
con committed
510

511
    // Check current project first
512
    Project *currentProject = ProjectExplorerPlugin::currentProject();
513 514
    if (currentProject && projectContainsFile(currentProject, fileName))
        return currentProject;
con's avatar
con committed
515

516 517 518 519 520 521 522 523 524 525 526 527
    foreach (Project *p, projectList)
        if (p != currentProject && projectContainsFile(p, fileName))
            return p;
    return 0;
}

bool SessionManager::projectContainsFile(Project *p, const QString &fileName) const
{
    if (!m_projectFileCache.contains(p))
        m_projectFileCache.insert(p, p->files(Project::AllFiles));

    return m_projectFileCache.value(p).contains(fileName);
con's avatar
con committed
528 529
}

530
void SessionManager::configureEditor(Core::IEditor *editor, const QString &fileName)
con's avatar
con committed
531
{
532 533 534
    if (TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor)) {
        Project *project = projectForFile(fileName);
        // Global settings are the default.
535 536
        if (project)
            project->editorConfiguration()->configureEditor(textEditor);
537
    }
con's avatar
con committed
538 539 540 541
}

void SessionManager::updateWindowTitle()
{
dt's avatar
dt committed
542
    if (isDefaultSession(m_sessionName)) {
543
        if (Project *currentProject = ProjectExplorerPlugin::currentProject())
hjk's avatar
hjk committed
544
            ICore::editorManager()->setWindowTitleAddition(currentProject->displayName());
545
        else
hjk's avatar
hjk committed
546
            ICore::editorManager()->setWindowTitleAddition(QString());
dt's avatar
dt committed
547
    } else {
con's avatar
con committed
548 549 550
        QString sessionName = m_sessionName;
        if (sessionName.isEmpty())
            sessionName = tr("Untitled");
hjk's avatar
hjk committed
551
        ICore::editorManager()->setWindowTitleAddition(sessionName);
552
    }
con's avatar
con committed
553 554 555 556 557 558 559 560
}

void SessionManager::removeProjects(QList<Project *> remove)
{
    QMap<QString, QStringList> resMap;

    foreach (Project *pro, remove) {
        if (debug)
561
            qDebug() << "SessionManager - emitting aboutToRemoveProject(" << pro->displayName() << ")";
con's avatar
con committed
562 563 564 565 566 567 568 569
        emit aboutToRemoveProject(pro);
    }


    // Refresh dependencies
    QSet<QString> projectFiles;
    foreach (Project *pro, projects()) {
        if (!remove.contains(pro))
570
            projectFiles.insert(pro->document()->fileName());
con's avatar
con committed
571 572 573 574 575
    }

    QSet<QString>::const_iterator i = projectFiles.begin();
    while (i != projectFiles.end()) {
        QStringList dependencies;
576
        foreach (const QString &dependency, m_depMap.value(*i)) {
con's avatar
con committed
577 578 579 580 581 582 583 584
            if (projectFiles.contains(dependency))
                dependencies << dependency;
        }
        if (!dependencies.isEmpty())
            resMap.insert(*i, dependencies);
        ++i;
    }

585
    m_depMap = resMap;
con's avatar
con committed
586

587 588 589 590 591
    // TODO: Clear m_modelProjectHash

    // Delete projects
    foreach (Project *pro, remove) {
        pro->saveSettings();
592
        m_projects.removeOne(pro);
593

594
        if (pro == m_startupProject)
595 596 597 598 599 600 601 602
            setStartupProject(0);

        disconnect(pro, SIGNAL(fileListChanged()),
                this, SLOT(clearProjectFileCache()));
        m_projectFileCache.remove(pro);

        if (debug)
            qDebug() << "SessionManager - emitting projectRemoved(" << pro->displayName() << ")";
603
        m_sessionNode->removeProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
604 605 606 607
        emit projectRemoved(pro);
        delete pro;
    }

con's avatar
con committed
608
    if (startupProject() == 0)
609 610
        if (!m_projects.isEmpty())
            setStartupProject(m_projects.first());
con's avatar
con committed
611 612
}

613 614 615 616
/*!
    \brief Let other plugins store persistent values within the session file.
*/

con's avatar
con committed
617 618
void SessionManager::setValue(const QString &name, const QVariant &value)
{
619
    if (m_values.value(name) == value)
620
        return;
621
    m_values.insert(name, value);
622
    markSessionFileDirty(false);
con's avatar
con committed
623 624 625 626
}

QVariant SessionManager::value(const QString &name)
{
627 628
    QMap<QString, QVariant>::const_iterator it = m_values.find(name);
    return (it == m_values.constEnd()) ? QVariant() : *it;
con's avatar
con committed
629 630 631 632 633 634 635 636 637
}

QString SessionManager::activeSession() const
{
    return m_sessionName;
}

QStringList SessionManager::sessions() const
{
638
    if (m_sessions.isEmpty()) {
Tobias Hunger's avatar
Tobias Hunger committed
639
        // We are not initialized yet, so do that now
hjk's avatar
hjk committed
640
        QDir sessionDir(Core::ICore::userResourcePath());
641 642
        QList<QFileInfo> sessionFiles = sessionDir.entryInfoList(QStringList() << QLatin1String("*.qws"), QDir::NoFilter, QDir::Time);
        Q_FOREACH(const QFileInfo& fileInfo, sessionFiles) {
643
            if (fileInfo.completeBaseName() != QLatin1String("default"))
644 645
                m_sessions << fileInfo.completeBaseName();
        }
646
        m_sessions.prepend(QLatin1String("default"));
647 648
    }
    return m_sessions;
con's avatar
con committed
649 650
}

651
Utils::FileName SessionManager::sessionNameToFileName(const QString &session) const
con's avatar
con committed
652
{
653
    return Utils::FileName::fromString(ICore::userResourcePath() + QLatin1Char('/') + session + QLatin1String(".qws"));
con's avatar
con committed
654 655
}

656 657 658 659
/*!
    \brief Just creates a new session (Does not actually create the file).
*/

con's avatar
con committed
660 661 662 663
bool SessionManager::createSession(const QString &session)
{
    if (sessions().contains(session))
        return false;
664 665
    Q_ASSERT(m_sessions.size() > 0);
    m_sessions.insert(1, session);
con's avatar
con committed
666 667 668
    return true;
}

669 670 671 672 673 674 675 676 677
bool SessionManager::renameSession(const QString &original, const QString &newName)
{
    if (!cloneSession(original, newName))
        return false;
    if (original == activeSession())
        loadSession(newName);
    return deleteSession(original);
}

678 679 680
/*!
     \brief Deletes session name from session list and file from disk.
*/
con's avatar
con committed
681 682
bool SessionManager::deleteSession(const QString &session)
{
683
    if (!m_sessions.contains(session))
con's avatar
con committed
684
        return false;
685
    m_sessions.removeOne(session);
686
    QFile fi(sessionNameToFileName(session).toString());
con's avatar
con committed
687 688 689 690 691 692 693
    if (fi.exists())
        return fi.remove();
    return false;
}

bool SessionManager::cloneSession(const QString &original, const QString &clone)
{
694
    if (!m_sessions.contains(original))
con's avatar
con committed
695 696
        return false;

697
    QFile fi(sessionNameToFileName(original).toString());
con's avatar
con committed
698
    // If the file does not exist, we can still clone
699
    if (!fi.exists() || fi.copy(sessionNameToFileName(clone).toString())) {
700 701
        Q_ASSERT(m_sessions.size() > 0);
        m_sessions.insert(1, clone);
con's avatar
con committed
702 703 704 705 706
        return true;
    }
    return false;
}

707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
void SessionManager::restoreValues(const Utils::PersistentSettingsReader &reader)
{
    const QStringList &keys = reader.restoreValue(QLatin1String("valueKeys")).toStringList();
    foreach (const QString &key, keys) {
        QVariant value = reader.restoreValue(QLatin1String("value-") + key);
        m_values.insert(key, value);
    }
}

void SessionManager::restoreDependencies(const Utils::PersistentSettingsReader &reader)
{
    QMap<QString, QVariant> depMap = reader.restoreValue(QLatin1String("ProjectDependencies")).toMap();
    QMap<QString, QVariant>::const_iterator i = depMap.constBegin();
    while (i != depMap.constEnd()) {
        const QString &key = i.key();
722 723 724 725 726 727 728
        if (!m_failedProjects.contains(key)) {
            QStringList values;
            foreach (const QString &value, i.value().toStringList()) {
                if (!m_failedProjects.contains(value))
                    values << value;
            }
            m_depMap.insert(key, values);
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
        }
        ++i;
    }
}

void SessionManager::askUserAboutFailedProjects()
{
    QStringList failedProjects = m_failedProjects;
    if (!failedProjects.isEmpty()) {
        QString fileList =
            QDir::toNativeSeparators(failedProjects.join(QLatin1String("<br>")));
        QMessageBox * box = new QMessageBox(QMessageBox::Warning,
                                            tr("Failed to restore project files"),
                                            tr("Could not restore the following project files:<br><b>%1</b>").
                                            arg(fileList));
        QPushButton * keepButton = new QPushButton(tr("Keep projects in Session"), box);
        QPushButton * removeButton = new QPushButton(tr("Remove projects from Session"), box);
        box->addButton(keepButton, QMessageBox::AcceptRole);
        box->addButton(removeButton, QMessageBox::DestructiveRole);

        box->exec();

        if (box->clickedButton() == removeButton)
            m_failedProjects.clear();
    }
}

void SessionManager::restoreStartupProject(const Utils::PersistentSettingsReader &reader)
{
    const QString startupProject = reader.restoreValue(QLatin1String("StartupProject")).toString();
    if (!startupProject.isEmpty()) {
        foreach (Project *pro, m_projects) {
761
            if (QDir::cleanPath(pro->document()->fileName()) == startupProject) {
762
                setStartupProject(pro);
763 764 765
                break;
            }
        }
766 767 768 769 770
    }
    if (!m_startupProject) {
        qWarning() << "Could not find startup project" << startupProject;
        if (!projects().isEmpty())
            setStartupProject(projects().first());
771 772 773 774 775 776 777
    }
}

void SessionManager::restoreEditors(const Utils::PersistentSettingsReader &reader)
{
    const QVariant &editorsettings = reader.restoreValue(QLatin1String("EditorSettings"));
    if (editorsettings.isValid()) {
Robert Loehning's avatar
Robert Loehning committed
778
        connect(ICore::editorManager(), SIGNAL(editorOpened(Core::IEditor*)),
779 780 781
                this, SLOT(sessionLoadingProgress()));
        ICore::editorManager()->restoreState(
            QByteArray::fromBase64(editorsettings.toByteArray()));
Robert Loehning's avatar
Robert Loehning committed
782
        disconnect(ICore::editorManager(), SIGNAL(editorOpened(Core::IEditor*)),
783 784 785 786
                   this, SLOT(sessionLoadingProgress()));
    }
}

787 788 789
/*!
     \brief Loads a session, takes a session name (not filename).
*/
790 791 792 793 794 795 796 797 798 799 800
void SessionManager::restoreProjects(const QStringList &fileList)
{
    // indirectly adds projects to session
    // Keep projects that failed to load in the session!
    m_failedProjects = fileList;
    if (!fileList.isEmpty()) {
        QString errors;
        QList<Project *> projects = ProjectExplorerPlugin::instance()->openProjects(fileList, &errors);
        if (!errors.isEmpty())
            QMessageBox::critical(Core::ICore::mainWindow(), tr("Failed to open project"), errors);
        foreach (Project *p, projects)
801
            m_failedProjects.removeAll(p->document()->fileName());
802 803
    }
}
804

con's avatar
con committed
805 806 807 808 809 810 811 812 813 814
bool SessionManager::loadSession(const QString &session)
{
    // Do nothing if we have that session already loaded,
    // exception if the session is the default virgin session
    // we still want to be able to load the default session
    if (session == m_sessionName && !isDefaultVirgin())
        return true;

    if (!sessions().contains(session))
        return false;
815

816
    // Try loading the file
817
    Utils::FileName fileName = sessionNameToFileName(session);
818
    PersistentSettingsReader reader;
819
    if (fileName.toFileInfo().exists()) {
820 821
        if (!reader.load(fileName)) {
            QMessageBox::warning(0, tr("Error while restoring session"),
822
                                 tr("Could not restore session %1").arg(fileName.toUserOutput()));
823 824 825 826
            return false;
        }
    }

827 828
    m_loadingSession = true;

829 830 831 832
    // Allow everyone to set something in the session and before saving
    emit aboutToUnloadSession(m_sessionName);

    if (!isDefaultVirgin()) {
833 834
        if (!save()) {
            m_loadingSession = false;
835
            return false;
836
        }
837 838 839
    }

    // Clean up
840 841
    if (!ICore::editorManager()->closeAllEditors()) {
        m_loadingSession = false;
842
        return false;
843
    }
844 845 846 847 848 849 850

    setStartupProject(0);
    removeProjects(projects());

    m_failedProjects.clear();
    m_depMap.clear();
    m_values.clear();
851

852 853 854
    m_sessionName = session;
    updateWindowTitle();

855
    if (fileName.toFileInfo().exists()) {
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881
        m_virginSession = false;

        ICore::progressManager()->addTask(m_future.future(), tr("Session"),
           QLatin1String("ProjectExplorer.SessionFile.Load"));

        restoreValues(reader);
        emit aboutToLoadSession(session);

        QStringList fileList =
            reader.restoreValue(QLatin1String("ProjectList")).toStringList();
        int openEditorsCount = reader.restoreValue(QLatin1String("OpenEditors")).toInt();

        m_future.setProgressRange(0, fileList.count() + openEditorsCount + 2);
        m_future.setProgressValue(1);

        restoreProjects(fileList);
        sessionLoadingProgress();
        restoreDependencies(reader);
        restoreStartupProject(reader);
        restoreEditors(reader);

        m_future.reportFinished();
        m_future = QFutureInterface<void>();

        // restore the active mode
        QString modeIdentifier = value(QLatin1String("ActiveMode")).toString();
882
        Id modeId;
883
        if (modeIdentifier.isEmpty())
884 885 886
            modeId = Id(Core::Constants::MODE_EDIT);
        else
            modeId = Id(modeIdentifier);
887

888
        ModeManager::activateMode(modeId);
889 890
        ModeManager::setFocusToCurrentMode();
    } else {
891
        ModeManager::activateMode(Id(Core::Constants::MODE_EDIT));
892 893 894 895 896 897
        ModeManager::setFocusToCurrentMode();
    }
    emit sessionLoaded(session);

    // Starts a event loop, better do that at the very end
    askUserAboutFailedProjects();
898
    m_loadingSession = false;
899
    return true;
con's avatar
con committed
900 901 902 903
}

QString SessionManager::lastSession() const
{
904
    return ICore::settings()->value(QLatin1String("ProjectExplorer/StartupSession")).toString();
con's avatar
con committed
905 906 907 908 909 910 911 912 913
}

SessionNode *SessionManager::sessionNode() const
{
    return m_sessionNode;
}

void SessionManager::reportProjectLoadingProgress()
{
914
    sessionLoadingProgress();
con's avatar
con committed
915 916
}

917
void SessionManager::markSessionFileDirty(bool makeDefaultVirginDirty)
918
{
919
    if (makeDefaultVirginDirty)
dt's avatar
dt committed
920
        m_virginSession = false;
921
}
con's avatar
con committed
922

923 924
void SessionManager::sessionLoadingProgress()
{
925
    m_future.setProgressValue(m_future.progressValue() + 1);
926 927
    QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
928

929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949
void SessionManager::projectDisplayNameChanged()
{
    Project *pro = qobject_cast<Project*>(sender());
    if (pro) {
        Node *currentNode = 0;
        if (ProjectExplorerPlugin::currentProject() == pro)
            currentNode = ProjectExplorerPlugin::instance()->currentNode();

        // Fix node sorting
        QList<ProjectNode *> nodes;
        nodes << pro->rootProjectNode();
        m_sessionNode->removeProjectNodes(nodes);
        m_sessionNode->addProjectNodes(nodes);

        if (currentNode)
            ProjectExplorerPlugin::instance()->setCurrentNode(currentNode);

        emit projectDisplayNameChanged(pro);
    }
}

950 951
QStringList ProjectExplorer::SessionManager::projectsForSessionName(const QString &session) const
{
952
    const Utils::FileName fileName = sessionNameToFileName(session);
953
    PersistentSettingsReader reader;
954
    if (fileName.toFileInfo().exists()) {
955
        if (!reader.load(fileName)) {
956
            qWarning() << "Could not restore session" << fileName.toUserOutput();
957 958 959 960 961
            return QStringList();
        }
    }
    return reader.restoreValue(QLatin1String("ProjectList")).toStringList();
}