session.cpp 29.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** 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
#include "project.h"
#include "projectexplorer.h"
#include "nodesvisitor.h"
#include "editorconfiguration.h"
36
#include "projectnodes.h"
con's avatar
con committed
37 38 39 40

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

#include <texteditor/itexteditor.h>

47
#include <utils/stylehelper.h>
hjk's avatar
hjk committed
48

49 50 51
#include <QDebug>
#include <QDir>
#include <QFileInfo>
hjk's avatar
hjk committed
52

53 54
#include <QMessageBox>
#include <QPushButton>
con's avatar
con committed
55 56 57 58 59

namespace {
    bool debug = false;
}

60
using namespace Core;
Jarek Kobus's avatar
Jarek Kobus committed
61 62
using Utils::PersistentSettingsReader;
using Utils::PersistentSettingsWriter;
63

con's avatar
con committed
64
using namespace ProjectExplorer;
65
using namespace ProjectExplorer::Internal;
con's avatar
con committed
66

67 68 69
/*!
     \class ProjectExplorer::SessionManager

70
     \brief The SessionManager class manages sessions.
71 72 73 74 75 76

     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
77

hjk's avatar
hjk committed
78 79
SessionManager::SessionManager(QObject *parent)
  : QObject(parent),
80
    m_sessionNode(new SessionNode(this)),
81
    m_sessionName(QLatin1String("default")),
82
    m_virginSession(true),
83
    m_loadingSession(false),
Tobias Hunger's avatar
Tobias Hunger committed
84 85
    m_startupProject(0),
    m_writer(0)
con's avatar
con committed
86
{
87
    connect(ModeManager::instance(), SIGNAL(currentModeChanged(Core::IMode*)),
con's avatar
con committed
88
            this, SLOT(saveActiveMode(Core::IMode*)));
89

90
    connect(EditorManager::instance(), SIGNAL(editorCreated(Core::IEditor*,QString)),
Robert Loehning's avatar
Robert Loehning committed
91 92
            this, SLOT(configureEditor(Core::IEditor*,QString)));
    connect(ProjectExplorerPlugin::instance(), SIGNAL(currentProjectChanged(ProjectExplorer::Project*)),
con's avatar
con committed
93
            this, SLOT(updateWindowTitle()));
94
    connect(EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)),
95
            this, SLOT(markSessionFileDirty()));
96
    connect(EditorManager::instance(), SIGNAL(editorsClosed(QList<Core::IEditor*>)),
97
            this, SLOT(markSessionFileDirty()));
hjk's avatar
hjk committed
98
}
con's avatar
con committed
99 100 101

SessionManager::~SessionManager()
{
102
    emit aboutToUnloadSession(m_sessionName);
Tobias Hunger's avatar
Tobias Hunger committed
103
    delete m_writer;
con's avatar
con committed
104 105 106 107 108
}


bool SessionManager::isDefaultVirgin() const
{
dt's avatar
dt committed
109 110
    return isDefaultSession(m_sessionName)
            && m_virginSession;
con's avatar
con committed
111 112 113 114
}

bool SessionManager::isDefaultSession(const QString &session) const
{
hjk's avatar
hjk committed
115
    return session == QLatin1String("default");
con's avatar
con committed
116 117 118 119 120
}


void SessionManager::saveActiveMode(Core::IMode *mode)
{
121
    setValue(QLatin1String("ActiveMode"), mode->id().toString());
con's avatar
con committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
}

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;

140
    foreach (const QString &dependency, m_depMap.value(checkDep)) {
con's avatar
con committed
141 142 143 144 145 146 147
        if (!recursiveDependencyCheck(newDep, dependency))
            return false;
    }

    return true;
}

148
/*
149
 * The dependency management exposes an interface based on projects, but
150 151 152 153 154 155 156
 * 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
{
157
    const QString &proName = project->projectFilePath();
158
    const QStringList &proDeps = m_depMap.value(proName);
159 160 161 162 163 164 165 166 167 168 169

    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
170
{
171 172
    const QString &proName = project->projectFilePath();
    const QString &depName = depProject->projectFilePath();
con's avatar
con committed
173

174
    const QStringList &proDeps = m_depMap.value(proName);
con's avatar
con committed
175 176 177
    return proDeps.contains(depName);
}

178
bool SessionManager::canAddDependency(const Project *project, const Project *depProject) const
con's avatar
con committed
179
{
180 181
    const QString &newDep = project->projectFilePath();
    const QString &checkDep = depProject->projectFilePath();
con's avatar
con committed
182 183 184 185

    return recursiveDependencyCheck(newDep, checkDep);
}

dt's avatar
dt committed
186
bool SessionManager::addDependency(Project *project, Project *depProject)
con's avatar
con committed
187
{
188 189
    const QString &proName = project->projectFilePath();
    const QString &depName = depProject->projectFilePath();
con's avatar
con committed
190 191 192 193 194

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

195
    QStringList proDeps = m_depMap.value(proName);
con's avatar
con committed
196 197
    if (!proDeps.contains(depName)) {
        proDeps.append(depName);
198
        m_depMap[proName] = proDeps;
con's avatar
con committed
199
    }
dt's avatar
dt committed
200
    emit dependencyChanged(project, depProject);
con's avatar
con committed
201 202 203 204

    return true;
}

dt's avatar
dt committed
205
void SessionManager::removeDependency(Project *project, Project *depProject)
206
{
207 208
    const QString &proName = project->projectFilePath();
    const QString &depName = depProject->projectFilePath();
209

210
    QStringList proDeps = m_depMap.value(proName);
211
    proDeps.removeAll(depName);
212
    if (proDeps.isEmpty())
213
        m_depMap.remove(proName);
214
    else
215
        m_depMap[proName] = proDeps;
dt's avatar
dt committed
216
    emit dependencyChanged(project, depProject);
217 218
}

con's avatar
con committed
219 220 221
void SessionManager::setStartupProject(Project *startupProject)
{
    if (debug)
222
        qDebug() << Q_FUNC_INFO << (startupProject ? startupProject->displayName() : QLatin1String("0"));
con's avatar
con committed
223 224

    if (startupProject) {
225
        Q_ASSERT(m_projects.contains(startupProject));
con's avatar
con committed
226 227
    }

228
    if (m_startupProject == startupProject)
229 230
        return;

231
    m_startupProject = startupProject;
con's avatar
con committed
232 233 234 235 236
    emit startupProjectChanged(startupProject);
}

Project *SessionManager::startupProject() const
{
237
    return m_startupProject;
con's avatar
con committed
238 239 240 241 242 243 244 245 246
}

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

void SessionManager::addProjects(const QList<Project*> &projects)
{
dt's avatar
dt committed
247
    m_virginSession = false;
con's avatar
con committed
248 249
    QList<Project*> clearedList;
    foreach (Project *pro, projects) {
250
        if (!m_projects.contains(pro)) {
con's avatar
con committed
251
            clearedList.append(pro);
252
            m_projects.append(pro);
253
            m_sessionNode->addProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
con's avatar
con committed
254 255 256 257

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

258 259 260
            connect(pro, SIGNAL(displayNameChanged()),
                    this, SLOT(projectDisplayNameChanged()));

con's avatar
con committed
261
            if (debug)
262
                qDebug() << "SessionManager - adding project " << pro->displayName();
con's avatar
con committed
263 264 265 266 267 268 269 270 271 272 273 274 275
        }
    }

    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
276
    m_virginSession = false;
con's avatar
con committed
277 278 279 280 281 282 283
    if (project == 0) {
        qDebug() << "SessionManager::removeProject(0) ... THIS SHOULD NOT HAPPEN";
        return;
    }
    removeProjects(QList<Project*>() << project);
}

284 285 286 287 288
bool SessionManager::loadingSession()
{
    return m_loadingSession;
}

con's avatar
con committed
289 290 291 292 293 294 295
bool SessionManager::save()
{
    if (debug)
        qDebug() << "SessionManager - saving session" << m_sessionName;

    emit aboutToSaveSession();

Tobias Hunger's avatar
Tobias Hunger committed
296 297 298 299 300
    if (!m_writer || m_writer->fileName() != sessionNameToFileName(m_sessionName)) {
        delete m_writer;
        m_writer = new Utils::PersistentSettingsWriter(sessionNameToFileName(m_sessionName),
                                                       QLatin1String("QtCreatorSession"));
    }
301

302
    QVariantMap data;
303
    // save the startup project
Tobias Hunger's avatar
Tobias Hunger committed
304
    if (m_startupProject)
305
        data.insert(QLatin1String("StartupProject"), m_startupProject->projectFilePath());
306

307 308 309 310 311 312
    QColor c = Utils::StyleHelper::requestedBaseColor();
    if (c.isValid()) {
        QString tmp = QString::fromLatin1("#%1%2%3")
                .arg(c.red(), 2, 16, QLatin1Char('0'))
                .arg(c.green(), 2, 16, QLatin1Char('0'))
                .arg(c.blue(), 2, 16, QLatin1Char('0'));
313
        data.insert(QLatin1String("Color"), tmp);
314 315
    }

316
    QStringList projectFiles;
317
    foreach (Project *pro, m_projects)
318
        projectFiles << pro->projectFilePath();
319 320

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

326
    data.insert(QLatin1String("ProjectList"), projectFiles);
327 328

    QMap<QString, QVariant> depMap;
329 330
    QMap<QString, QStringList>::const_iterator i = m_depMap.constBegin();
    while (i != m_depMap.constEnd()) {
331 332 333 334 335 336 337 338
        QString key = i.key();
        QStringList values;
        foreach (const QString &value, i.value()) {
            values << value;
        }
        depMap.insert(key, values);
        ++i;
    }
339
    data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
340
    data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
341 342

    QMap<QString, QVariant>::const_iterator it, end;
343
    end = m_values.constEnd();
344
    QStringList keys;
345
    for (it = m_values.constBegin(); it != end; ++it) {
346
        data.insert(QLatin1String("value-") + it.key(), it.value());
347 348 349
        keys << it.key();
    }

350
    data.insert(QLatin1String("valueKeys"), keys);
351

352
    bool result = m_writer->save(data, Core::ICore::mainWindow());
con's avatar
con committed
353 354
    if (!result) {
        QMessageBox::warning(0, tr("Error while saving session"),
Tobias Hunger's avatar
Tobias Hunger committed
355
                                tr("Could not save session to file %1").arg(m_writer->fileName().toUserOutput()));
con's avatar
con committed
356 357 358 359 360 361 362 363 364
    }

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

    return result;
}

/*!
365
  Closes all projects
con's avatar
con committed
366
  */
367
void SessionManager::closeAllProjects()
con's avatar
con committed
368
{
369 370
    setStartupProject(0);
    removeProjects(projects());
con's avatar
con committed
371 372
}

373
const QList<Project *> &SessionManager::projects() const
con's avatar
con committed
374
{
375
    return m_projects;
con's avatar
con committed
376 377 378 379 380
}

QStringList SessionManager::dependencies(const QString &proName) const
{
    QStringList result;
381
    dependencies(proName, result);
con's avatar
con committed
382 383 384
    return result;
}

385 386 387 388 389 390 391 392 393 394 395
void SessionManager::dependencies(const QString &proName, QStringList &result) const
{
    QStringList depends = m_depMap.value(proName);

    foreach (const QString &dep, depends)
        dependencies(dep, result);

    if (!result.contains(proName))
        result.append(proName);
}

con's avatar
con committed
396 397 398 399 400 401 402
QStringList SessionManager::dependenciesOrder() const
{
    QList<QPair<QString, QStringList> > unordered;
    QStringList ordered;

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

    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;
435
    if (project)
436
        pros = dependencies(project->projectFilePath());
437
    else
con's avatar
con committed
438 439 440 441
        pros = dependenciesOrder();

    foreach (const QString &proFile, pros) {
        foreach (Project *pro, projects()) {
442
            if (pro->projectFilePath() == proFile) {
con's avatar
con committed
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
                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
465
    Q_ASSERT(rootProjectNode);
con's avatar
con committed
466 467 468 469 470 471 472 473 474 475 476 477

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

    return project;
}

dt's avatar
dt committed
478
Node *SessionManager::nodeForFile(const QString &fileName, Project *project) const
con's avatar
con committed
479 480
{
    Node *node = 0;
dt's avatar
dt committed
481 482 483
    if (!project)
        project = projectForFile(fileName);
    if (project) {
con's avatar
con committed
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
        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 << ")";

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

504
    // Check current project first
505
    Project *currentProject = ProjectExplorerPlugin::currentProject();
506 507
    if (currentProject && projectContainsFile(currentProject, fileName))
        return currentProject;
con's avatar
con committed
508

509 510 511 512 513 514 515 516 517 518 519 520
    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
521 522
}

523
void SessionManager::configureEditor(Core::IEditor *editor, const QString &fileName)
con's avatar
con committed
524
{
525 526 527
    if (TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor)) {
        Project *project = projectForFile(fileName);
        // Global settings are the default.
528 529
        if (project)
            project->editorConfiguration()->configureEditor(textEditor);
530
    }
con's avatar
con committed
531 532 533 534
}

void SessionManager::updateWindowTitle()
{
dt's avatar
dt committed
535
    if (isDefaultSession(m_sessionName)) {
536
        if (Project *currentProject = ProjectExplorerPlugin::currentProject())
537
            EditorManager::setWindowTitleAddition(currentProject->displayName());
538
        else
539
            EditorManager::setWindowTitleAddition(QString());
dt's avatar
dt committed
540
    } else {
con's avatar
con committed
541 542 543
        QString sessionName = m_sessionName;
        if (sessionName.isEmpty())
            sessionName = tr("Untitled");
544
        EditorManager::setWindowTitleAddition(sessionName);
545
    }
con's avatar
con committed
546 547 548 549 550 551 552 553
}

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

    foreach (Project *pro, remove) {
        if (debug)
554
            qDebug() << "SessionManager - emitting aboutToRemoveProject(" << pro->displayName() << ")";
con's avatar
con committed
555 556 557 558 559 560 561 562
        emit aboutToRemoveProject(pro);
    }


    // Refresh dependencies
    QSet<QString> projectFiles;
    foreach (Project *pro, projects()) {
        if (!remove.contains(pro))
563
            projectFiles.insert(pro->projectFilePath());
con's avatar
con committed
564 565 566 567 568
    }

    QSet<QString>::const_iterator i = projectFiles.begin();
    while (i != projectFiles.end()) {
        QStringList dependencies;
569
        foreach (const QString &dependency, m_depMap.value(*i)) {
con's avatar
con committed
570 571 572 573 574 575 576 577
            if (projectFiles.contains(dependency))
                dependencies << dependency;
        }
        if (!dependencies.isEmpty())
            resMap.insert(*i, dependencies);
        ++i;
    }

578
    m_depMap = resMap;
con's avatar
con committed
579

580 581 582 583 584
    // TODO: Clear m_modelProjectHash

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

587
        if (pro == m_startupProject)
588 589 590 591 592 593 594 595
            setStartupProject(0);

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

        if (debug)
            qDebug() << "SessionManager - emitting projectRemoved(" << pro->displayName() << ")";
596
        m_sessionNode->removeProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
597 598 599 600
        emit projectRemoved(pro);
        delete pro;
    }

con's avatar
con committed
601
    if (startupProject() == 0)
602 603
        if (!m_projects.isEmpty())
            setStartupProject(m_projects.first());
con's avatar
con committed
604 605
}

606 607 608 609
/*!
    \brief Let other plugins store persistent values within the session file.
*/

con's avatar
con committed
610 611
void SessionManager::setValue(const QString &name, const QVariant &value)
{
612
    if (m_values.value(name) == value)
613
        return;
614
    m_values.insert(name, value);
615
    markSessionFileDirty(false);
con's avatar
con committed
616 617 618 619
}

QVariant SessionManager::value(const QString &name)
{
620 621
    QMap<QString, QVariant>::const_iterator it = m_values.find(name);
    return (it == m_values.constEnd()) ? QVariant() : *it;
con's avatar
con committed
622 623 624 625 626 627 628 629 630
}

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

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

644
Utils::FileName SessionManager::sessionNameToFileName(const QString &session) const
con's avatar
con committed
645
{
646
    return Utils::FileName::fromString(ICore::userResourcePath() + QLatin1Char('/') + session + QLatin1String(".qws"));
con's avatar
con committed
647 648
}

649 650 651 652
/*!
    \brief Just creates a new session (Does not actually create the file).
*/

con's avatar
con committed
653 654 655 656
bool SessionManager::createSession(const QString &session)
{
    if (sessions().contains(session))
        return false;
657 658
    Q_ASSERT(m_sessions.size() > 0);
    m_sessions.insert(1, session);
con's avatar
con committed
659 660 661
    return true;
}

662 663 664 665 666 667 668 669 670
bool SessionManager::renameSession(const QString &original, const QString &newName)
{
    if (!cloneSession(original, newName))
        return false;
    if (original == activeSession())
        loadSession(newName);
    return deleteSession(original);
}

671 672 673 674 675 676 677 678 679 680 681 682

/*!
    \brief Shows a dialog asking the user to confirm deleting the session \p session
*/
bool SessionManager::confirmSessionDelete(const QString &session)
{
    return QMessageBox::question(Core::ICore::mainWindow(),
                                 tr("Delete Session"),
                                 tr("Delete session %1?").arg(session),
                                 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes;
}

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

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

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

712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
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();
727 728 729 730 731 732 733
        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);
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 761 762 763 764 765
        }
        ++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) {
766
            if (QDir::cleanPath(pro->projectFilePath()) == startupProject) {
767
                setStartupProject(pro);
768 769 770
                break;
            }
        }
771 772 773 774 775
    }
    if (!m_startupProject) {
        qWarning() << "Could not find startup project" << startupProject;
        if (!projects().isEmpty())
            setStartupProject(projects().first());
776 777 778 779 780 781 782
    }
}

void SessionManager::restoreEditors(const Utils::PersistentSettingsReader &reader)
{
    const QVariant &editorsettings = reader.restoreValue(QLatin1String("EditorSettings"));
    if (editorsettings.isValid()) {
783
        EditorManager::restoreState(QByteArray::fromBase64(editorsettings.toByteArray()));
784
        sessionLoadingProgress();
785 786 787
    }
}

788 789 790
/*!
     \brief Loads a session, takes a session name (not filename).
*/
791 792 793 794 795 796 797 798 799 800 801
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)
802
            m_failedProjects.removeAll(p->projectFilePath());
803 804
    }
}
805

con's avatar
con committed
806 807 808 809 810 811 812 813 814 815
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;
816

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

828 829
    m_loadingSession = true;

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

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

    // Clean up
841
    if (!EditorManager::closeAllEditors()) {
842
        m_loadingSession = false;
843
        return false;
844
    }
845 846 847 848 849 850 851

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

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

853 854 855
    m_sessionName = session;
    updateWindowTitle();

856
    if (fileName.toFileInfo().exists()) {
857 858
        m_virginSession = false;

859
        ProgressManager::addTask(m_future.future(), tr("Session"),
860
           "ProjectExplorer.SessionFile.Load");
861

862 863 864
        m_future.setProgressRange(0, 1);
        m_future.setProgressValue(0);

865 866 867
        restoreValues(reader);
        emit aboutToLoadSession(session);

868 869 870 871
        QColor c = QColor(reader.restoreValue(QLatin1String("Color")).toString());
        if (c.isValid())
            Utils::StyleHelper::setBaseColor(c);

872 873 874
        QStringList fileList =
            reader.restoreValue(QLatin1String("ProjectList")).toStringList();

875
        m_future.setProgressRange(0, fileList.count() + 1/*initialization above*/ + 1/*editors*/);
876 877
        m_future.setProgressValue(1);

878 879 880 881
        // if one processEvents doesn't get the job done
        // just use two!
        QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
        QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
882 883 884 885 886 887 888 889 890 891
        restoreProjects(fileList);
        sessionLoadingProgress();
        restoreDependencies(reader);
        restoreStartupProject(reader);
        restoreEditors(reader);

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

        // restore the active mode
892 893
        Id modeId = Id::fromSetting(value(QLatin1String("ActiveMode")));
        if (!modeId.isValid())
894
            modeId = Id(Core::Constants::MODE_EDIT);
895

896
        ModeManager::activateMode(modeId);
897 898
        ModeManager::setFocusToCurrentMode();
    } else {
899
        ModeManager::activateMode(Id(Core::Constants::MODE_EDIT));
900 901 902 903 904 905
        ModeManager::setFocusToCurrentMode();
    }
    emit sessionLoaded(session);

    // Starts a event loop, better do that at the very end
    askUserAboutFailedProjects();
906
    m_loadingSession = false;
907
    return true;
con's avatar
con committed
908 909 910 911
}

QString SessionManager::lastSession() const
{
912
    return ICore::settings()->value(QLatin1String("ProjectExplorer/StartupSession")).toString();
con's avatar
con committed
913 914 915 916 917 918 919 920 921
}

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

void SessionManager::reportProjectLoadingProgress()
{
922
    sessionLoadingProgress();
con's avatar
con committed
923 924
}

925
void SessionManager::markSessionFileDirty(bool makeDefaultVirginDirty)
926
{
927
    if (makeDefaultVirginDirty)
dt's avatar
dt committed
928
        m_virginSession = false;
929
}
con's avatar
con committed
930

931 932
void SessionManager::sessionLoadingProgress()
{
933
    m_future.setProgressValue(m_future.progressValue() + 1);
934 935
    QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
936

937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957
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);
    }
}

958 959
QStringList ProjectExplorer::SessionManager::projectsForSessionName(const QString &session) const
{
960
    const Utils::FileName fileName = sessionNameToFileName(session);
961
    PersistentSettingsReader reader;
962
    if (fileName.toFileInfo().exists()) {
963
        if (!reader.load(fileName)) {
964
            qWarning() << "Could not restore session" << fileName.toUserOutput();
965 966 967 968 969
            return QStringList();
        }
    }
    return reader.restoreValue(QLatin1String("ProjectList")).toStringList();
}