session.cpp 32.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 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
#include <coreplugin/modemanager.h>

45
#include <texteditor/basetexteditor.h>
con's avatar
con committed
46

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

51 52 53
#include <QDebug>
#include <QDir>
#include <QFileInfo>
hjk's avatar
hjk committed
54

55 56
#include <QMessageBox>
#include <QPushButton>
con's avatar
con committed
57

hjk's avatar
hjk committed
58
namespace { bool debug = false; }
con's avatar
con committed
59

60
using namespace Core;
hjk's avatar
hjk committed
61
using namespace Utils;
62
using namespace ProjectExplorer::Internal;
con's avatar
con committed
63

hjk's avatar
hjk committed
64 65
namespace ProjectExplorer {

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

69
     \brief The SessionManager class manages sessions.
70 71

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

hjk's avatar
hjk committed
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
class SessionManagerPrivate
{
public:
    SessionManagerPrivate() :
        m_sessionName(QLatin1String("default")),
        m_virginSession(true),
        m_loadingSession(false),
        m_startupProject(0),
        m_writer(0)
    {}

    bool projectContainsFile(Project *p, const QString &fileName) const;
    void restoreValues(const PersistentSettingsReader &reader);
    void restoreDependencies(const PersistentSettingsReader &reader);
    void restoreStartupProject(const PersistentSettingsReader &reader);
    void restoreEditors(const PersistentSettingsReader &reader);
    void restoreProjects(const QStringList &fileList);
    void askUserAboutFailedProjects();
    void sessionLoadingProgress();

    bool recursiveDependencyCheck(const QString &newDep, const QString &checkDep) const;
    QStringList dependencies(const QString &proName) const;
    QStringList dependenciesOrder() const;
    void dependencies(const QString &proName, QStringList &result) const;

public:
103 104
    static QString windowTitleAddition(const QString &filePath);

hjk's avatar
hjk committed
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
    SessionNode *m_sessionNode;
    QString m_sessionName;
    bool m_virginSession;

    mutable QStringList m_sessions;

    mutable QHash<Project *, QStringList> m_projectFileCache;
    bool m_loadingSession;

    Project *m_startupProject;
    QList<Project *> m_projects;
    QStringList m_failedProjects;
    QMap<QString, QStringList> m_depMap;
    QMap<QString, QVariant> m_values;
    QFutureInterface<void> m_future;
    PersistentSettingsWriter *m_writer;
};

static SessionManager *m_instance = 0;
static SessionManagerPrivate *d = 0;

hjk's avatar
hjk committed
126
SessionManager::SessionManager(QObject *parent)
hjk's avatar
hjk committed
127
  : QObject(parent)
con's avatar
con committed
128
{
hjk's avatar
hjk committed
129 130 131 132 133
    m_instance = this;
    d = new SessionManagerPrivate;

    d->m_sessionNode = new SessionNode(this);

134
    connect(ModeManager::instance(), SIGNAL(currentModeChanged(Core::IMode*)),
con's avatar
con committed
135
            this, SLOT(saveActiveMode(Core::IMode*)));
136

137
    connect(EditorManager::instance(), SIGNAL(editorCreated(Core::IEditor*,QString)),
Robert Loehning's avatar
Robert Loehning committed
138
            this, SLOT(configureEditor(Core::IEditor*,QString)));
139 140 141 142 143 144
    connect(this, SIGNAL(projectAdded(ProjectExplorer::Project*)),
            EditorManager::instance(), SLOT(updateWindowTitles()));
    connect(this, SIGNAL(projectRemoved(ProjectExplorer::Project*)),
            EditorManager::instance(), SLOT(updateWindowTitles()));
    connect(this, SIGNAL(projectDisplayNameChanged(ProjectExplorer::Project*)),
            EditorManager::instance(), SLOT(updateWindowTitles()));
145
    connect(EditorManager::instance(), SIGNAL(editorOpened(Core::IEditor*)),
146
            this, SLOT(markSessionFileDirty()));
147
    connect(EditorManager::instance(), SIGNAL(editorsClosed(QList<Core::IEditor*>)),
148
            this, SLOT(markSessionFileDirty()));
149 150

    EditorManager::setWindowTitleAdditionHandler(&SessionManagerPrivate::windowTitleAddition);
hjk's avatar
hjk committed
151
}
con's avatar
con committed
152 153 154

SessionManager::~SessionManager()
{
hjk's avatar
hjk committed
155 156 157
    emit m_instance->aboutToUnloadSession(d->m_sessionName);
    delete d->m_writer;
    delete d;
con's avatar
con committed
158 159
}

hjk's avatar
hjk committed
160 161 162 163
QObject *SessionManager::instance()
{
   return m_instance;
}
con's avatar
con committed
164

hjk's avatar
hjk committed
165
bool SessionManager::isDefaultVirgin()
con's avatar
con committed
166
{
hjk's avatar
hjk committed
167
    return isDefaultSession(d->m_sessionName) && d->m_virginSession;
con's avatar
con committed
168 169
}

hjk's avatar
hjk committed
170
bool SessionManager::isDefaultSession(const QString &session)
con's avatar
con committed
171
{
hjk's avatar
hjk committed
172
    return session == QLatin1String("default");
con's avatar
con committed
173 174 175 176 177
}


void SessionManager::saveActiveMode(Core::IMode *mode)
{
178
    setValue(QLatin1String("ActiveMode"), mode->id().toString());
con's avatar
con committed
179 180 181 182 183 184
}

void SessionManager::clearProjectFileCache()
{
    // If triggered by the fileListChanged signal of one project
    // only invalidate cache for this project
hjk's avatar
hjk committed
185
    Project *pro = qobject_cast<Project*>(m_instance->sender());
con's avatar
con committed
186
    if (pro)
hjk's avatar
hjk committed
187
        d->m_projectFileCache.remove(pro);
con's avatar
con committed
188
    else
hjk's avatar
hjk committed
189
        d->m_projectFileCache.clear();
con's avatar
con committed
190 191
}

hjk's avatar
hjk committed
192
bool SessionManagerPrivate::recursiveDependencyCheck(const QString &newDep, const QString &checkDep) const
con's avatar
con committed
193 194 195 196
{
    if (newDep == checkDep)
        return false;

hjk's avatar
hjk committed
197
    foreach (const QString &dependency, m_depMap.value(checkDep))
con's avatar
con committed
198 199 200 201 202 203
        if (!recursiveDependencyCheck(newDep, dependency))
            return false;

    return true;
}

204
/*
205
 * The dependency management exposes an interface based on projects, but
206 207 208 209 210
 * 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.
 */

hjk's avatar
hjk committed
211
QList<Project *> SessionManager::dependencies(const Project *project)
212
{
213
    const QString proName = project->projectFilePath().toString();
hjk's avatar
hjk committed
214
    const QStringList proDeps = d->m_depMap.value(proName);
215 216 217 218 219 220 221 222 223 224

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

    return projects;
}

hjk's avatar
hjk committed
225
bool SessionManager::hasDependency(const Project *project, const Project *depProject)
con's avatar
con committed
226
{
227 228
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
con's avatar
con committed
229

hjk's avatar
hjk committed
230
    const QStringList proDeps = d->m_depMap.value(proName);
con's avatar
con committed
231 232 233
    return proDeps.contains(depName);
}

hjk's avatar
hjk committed
234
bool SessionManager::canAddDependency(const Project *project, const Project *depProject)
con's avatar
con committed
235
{
236 237
    const QString newDep = project->projectFilePath().toString();
    const QString checkDep = depProject->projectFilePath().toString();
con's avatar
con committed
238

hjk's avatar
hjk committed
239
    return d->recursiveDependencyCheck(newDep, checkDep);
con's avatar
con committed
240 241
}

dt's avatar
dt committed
242
bool SessionManager::addDependency(Project *project, Project *depProject)
con's avatar
con committed
243
{
244 245
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
con's avatar
con committed
246 247

    // check if this dependency is valid
hjk's avatar
hjk committed
248
    if (!d->recursiveDependencyCheck(proName, depName))
con's avatar
con committed
249 250
        return false;

hjk's avatar
hjk committed
251
    QStringList proDeps = d->m_depMap.value(proName);
con's avatar
con committed
252 253
    if (!proDeps.contains(depName)) {
        proDeps.append(depName);
hjk's avatar
hjk committed
254
        d->m_depMap[proName] = proDeps;
con's avatar
con committed
255
    }
hjk's avatar
hjk committed
256
    emit m_instance->dependencyChanged(project, depProject);
con's avatar
con committed
257 258 259 260

    return true;
}

dt's avatar
dt committed
261
void SessionManager::removeDependency(Project *project, Project *depProject)
262
{
263 264
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
265

hjk's avatar
hjk committed
266
    QStringList proDeps = d->m_depMap.value(proName);
267
    proDeps.removeAll(depName);
268
    if (proDeps.isEmpty())
hjk's avatar
hjk committed
269
        d->m_depMap.remove(proName);
270
    else
hjk's avatar
hjk committed
271 272
        d->m_depMap[proName] = proDeps;
    emit m_instance->dependencyChanged(project, depProject);
273 274
}

con's avatar
con committed
275 276 277
void SessionManager::setStartupProject(Project *startupProject)
{
    if (debug)
278
        qDebug() << Q_FUNC_INFO << (startupProject ? startupProject->displayName() : QLatin1String("0"));
con's avatar
con committed
279 280

    if (startupProject) {
hjk's avatar
hjk committed
281
        Q_ASSERT(d->m_projects.contains(startupProject));
con's avatar
con committed
282 283
    }

hjk's avatar
hjk committed
284
    if (d->m_startupProject == startupProject)
285 286
        return;

hjk's avatar
hjk committed
287 288
    d->m_startupProject = startupProject;
    emit m_instance->startupProjectChanged(startupProject);
con's avatar
con committed
289 290
}

hjk's avatar
hjk committed
291
Project *SessionManager::startupProject()
con's avatar
con committed
292
{
hjk's avatar
hjk committed
293
    return d->m_startupProject;
con's avatar
con committed
294 295 296 297 298 299 300 301 302
}

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

void SessionManager::addProjects(const QList<Project*> &projects)
{
hjk's avatar
hjk committed
303
    d->m_virginSession = false;
con's avatar
con committed
304 305
    QList<Project*> clearedList;
    foreach (Project *pro, projects) {
hjk's avatar
hjk committed
306
        if (!d->m_projects.contains(pro)) {
con's avatar
con committed
307
            clearedList.append(pro);
hjk's avatar
hjk committed
308 309
            d->m_projects.append(pro);
            d->m_sessionNode->addProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
con's avatar
con committed
310 311

            connect(pro, SIGNAL(fileListChanged()),
hjk's avatar
hjk committed
312
                    m_instance, SLOT(clearProjectFileCache()));
con's avatar
con committed
313

314
            connect(pro, SIGNAL(displayNameChanged()),
hjk's avatar
hjk committed
315
                    m_instance, SLOT(projectDisplayNameChanged()));
316

con's avatar
con committed
317
            if (debug)
318
                qDebug() << "SessionManager - adding project " << pro->displayName();
con's avatar
con committed
319 320 321
        }
    }

hjk's avatar
hjk committed
322 323
    foreach (Project *pro, clearedList)
        emit m_instance->projectAdded(pro);
con's avatar
con committed
324 325

    if (clearedList.count() == 1)
hjk's avatar
hjk committed
326
        emit m_instance->singleProjectAdded(clearedList.first());
con's avatar
con committed
327 328 329 330
}

void SessionManager::removeProject(Project *project)
{
hjk's avatar
hjk committed
331
    d->m_virginSession = false;
con's avatar
con committed
332 333 334 335 336 337 338
    if (project == 0) {
        qDebug() << "SessionManager::removeProject(0) ... THIS SHOULD NOT HAPPEN";
        return;
    }
    removeProjects(QList<Project*>() << project);
}

339 340
bool SessionManager::loadingSession()
{
hjk's avatar
hjk committed
341
    return d->m_loadingSession;
342 343
}

con's avatar
con committed
344 345 346
bool SessionManager::save()
{
    if (debug)
hjk's avatar
hjk committed
347
        qDebug() << "SessionManager - saving session" << d->m_sessionName;
con's avatar
con committed
348

hjk's avatar
hjk committed
349
    emit m_instance->aboutToSaveSession();
con's avatar
con committed
350

hjk's avatar
hjk committed
351 352 353
    if (!d->m_writer || d->m_writer->fileName() != sessionNameToFileName(d->m_sessionName)) {
        delete d->m_writer;
        d->m_writer = new PersistentSettingsWriter(sessionNameToFileName(d->m_sessionName),
Tobias Hunger's avatar
Tobias Hunger committed
354 355
                                                       QLatin1String("QtCreatorSession"));
    }
356

357
    QVariantMap data;
358
    // save the startup project
hjk's avatar
hjk committed
359
    if (d->m_startupProject)
360
        data.insert(QLatin1String("StartupProject"), d->m_startupProject->projectFilePath().toString());
361

hjk's avatar
hjk committed
362
    QColor c = StyleHelper::requestedBaseColor();
363 364 365 366 367
    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'));
368
        data.insert(QLatin1String("Color"), tmp);
369 370
    }

371
    QStringList projectFiles;
hjk's avatar
hjk committed
372
    foreach (Project *pro, d->m_projects)
373
        projectFiles << pro->projectFilePath().toString();
374 375

    // Restore infromation on projects that failed to load:
376
    // don't readd projects to the list, which the user loaded
hjk's avatar
hjk committed
377
    foreach (const QString &failed, d->m_failedProjects)
378 379
        if (!projectFiles.contains(failed))
            projectFiles << failed;
380

381
    data.insert(QLatin1String("ProjectList"), projectFiles);
382 383

    QMap<QString, QVariant> depMap;
hjk's avatar
hjk committed
384 385
    QMap<QString, QStringList>::const_iterator i = d->m_depMap.constBegin();
    while (i != d->m_depMap.constEnd()) {
386 387 388 389 390 391 392 393
        QString key = i.key();
        QStringList values;
        foreach (const QString &value, i.value()) {
            values << value;
        }
        depMap.insert(key, values);
        ++i;
    }
394
    data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
395
    data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
396

hjk's avatar
hjk committed
397
    QMap<QString, QVariant>::const_iterator it, end = d->m_values.constEnd();
398
    QStringList keys;
hjk's avatar
hjk committed
399
    for (it = d->m_values.constBegin(); it != end; ++it) {
400
        data.insert(QLatin1String("value-") + it.key(), it.value());
401 402 403
        keys << it.key();
    }

404
    data.insert(QLatin1String("valueKeys"), keys);
405

hjk's avatar
hjk committed
406
    bool result = d->m_writer->save(data, Core::ICore::mainWindow());
con's avatar
con committed
407
    if (!result) {
408
        QMessageBox::warning(ICore::dialogParent(), tr("Error while saving session"),
hjk's avatar
hjk committed
409
            tr("Could not save session to file %1").arg(d->m_writer->fileName().toUserOutput()));
con's avatar
con committed
410 411 412 413 414 415 416 417 418
    }

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

    return result;
}

/*!
419
  Closes all projects
con's avatar
con committed
420
  */
421
void SessionManager::closeAllProjects()
con's avatar
con committed
422
{
423 424
    setStartupProject(0);
    removeProjects(projects());
con's avatar
con committed
425 426
}

hjk's avatar
hjk committed
427 428 429 430 431 432
const QList<Project *> &SessionManager::projects()
{
    return d->m_projects;
}

bool SessionManager::hasProjects()
con's avatar
con committed
433
{
hjk's avatar
hjk committed
434
    return !d->m_projects.isEmpty();
con's avatar
con committed
435 436
}

hjk's avatar
hjk committed
437
QStringList SessionManagerPrivate::dependencies(const QString &proName) const
con's avatar
con committed
438 439
{
    QStringList result;
440
    dependencies(proName, result);
con's avatar
con committed
441 442 443
    return result;
}

hjk's avatar
hjk committed
444
void SessionManagerPrivate::dependencies(const QString &proName, QStringList &result) const
445 446 447 448 449 450 451 452 453 454
{
    QStringList depends = m_depMap.value(proName);

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

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

455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
QString SessionManagerPrivate::windowTitleAddition(const QString &filePath)
{
    if (SessionManager::isDefaultSession(d->m_sessionName)) {
        if (filePath.isEmpty()) {
            // use single project's name if there is only one loaded.
            const QList<ProjectExplorer::Project *> projects = ProjectExplorer::SessionManager::projects();
            if (projects.size() == 1)
                return projects.first()->displayName();
            return QString();
        } else if (Project *project = SessionManager::projectForFile(filePath)) {
            return project->displayName();
        } else {
            return QString();
        }
    } else {
        QString sessionName = d->m_sessionName;
        if (sessionName.isEmpty())
            sessionName = SessionManager::tr("Untitled");
        return sessionName;
    }
}

hjk's avatar
hjk committed
477
QStringList SessionManagerPrivate::dependenciesOrder() const
con's avatar
con committed
478 479 480 481 482
{
    QList<QPair<QString, QStringList> > unordered;
    QStringList ordered;

    // copy the map to a temporary list
hjk's avatar
hjk committed
483
    foreach (Project *pro, m_projects) {
484
        const QString &proName = pro->projectFilePath().toString();
hjk's avatar
hjk committed
485
        unordered << QPair<QString, QStringList>(proName, m_depMap.value(proName));
con's avatar
con committed
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    }

    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;
}

hjk's avatar
hjk committed
510
QList<Project *> SessionManager::projectOrder(Project *project)
con's avatar
con committed
511 512 513 514
{
    QList<Project *> result;

    QStringList pros;
515
    if (project)
516
        pros = d->dependencies(project->projectFilePath().toString());
517
    else
hjk's avatar
hjk committed
518
        pros = d->dependenciesOrder();
con's avatar
con committed
519 520 521

    foreach (const QString &proFile, pros) {
        foreach (Project *pro, projects()) {
522
            if (pro->projectFilePath().toString() == proFile) {
con's avatar
con committed
523 524 525 526 527 528 529 530 531
                result << pro;
                break;
            }
        }
    }

    return result;
}

hjk's avatar
hjk committed
532
Project *SessionManager::projectForNode(Node *node)
con's avatar
con committed
533 534 535 536 537 538 539
{
    if (!node)
        return 0;

    FolderNode *rootProjectNode = qobject_cast<FolderNode*>(node);
    if (!rootProjectNode)
        rootProjectNode = node->parentFolderNode();
hjk's avatar
hjk committed
540 541

    while (rootProjectNode && rootProjectNode->parentFolderNode() != d->m_sessionNode)
con's avatar
con committed
542 543
        rootProjectNode = rootProjectNode->parentFolderNode();

dt's avatar
dt committed
544
    Q_ASSERT(rootProjectNode);
con's avatar
con committed
545

546
    return Utils::findOrDefault(d->m_projects, Utils::equal(&Project::rootProjectNode, rootProjectNode));
con's avatar
con committed
547 548
}

549
QList<Node *> SessionManager::nodesForFile(const QString &fileName, Project *project)
con's avatar
con committed
550
{
dt's avatar
dt committed
551 552
    if (!project)
        project = projectForFile(fileName);
hjk's avatar
hjk committed
553

dt's avatar
dt committed
554
    if (project) {
con's avatar
con committed
555 556
        FindNodesForFileVisitor findNodes(fileName);
        project->rootProjectNode()->accept(&findNodes);
557
        return findNodes.nodes();
con's avatar
con committed
558 559
    }

560 561 562 563 564 565 566 567 568 569 570 571 572
    return QList<Node *>();
}

// node for file returns a randomly selected node if there are multiple
// prefer to use nodesForFile and figure out which node you want
Node *SessionManager::nodeForFile(const QString &fileName, Project *project)
{
    Node *node = 0;
    foreach (Node *n, nodesForFile(fileName, project)) {
        // prefer file nodes
        if (!node || (node->nodeType() != FileNodeType && n->nodeType() == FileNodeType))
            node = n;
    }
con's avatar
con committed
573 574 575
    return node;
}

hjk's avatar
hjk committed
576
Project *SessionManager::projectForFile(const QString &fileName)
con's avatar
con committed
577 578 579 580
{
    if (debug)
        qDebug() << "SessionManager::projectForFile(" << fileName << ")";

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

583
    // Check current project first
584
    Project *currentProject = ProjectExplorerPlugin::currentProject();
hjk's avatar
hjk committed
585
    if (currentProject && d->projectContainsFile(currentProject, fileName))
586
        return currentProject;
con's avatar
con committed
587

588
    foreach (Project *p, projectList)
hjk's avatar
hjk committed
589
        if (p != currentProject && d->projectContainsFile(p, fileName))
590
            return p;
hjk's avatar
hjk committed
591

592 593 594
    return 0;
}

hjk's avatar
hjk committed
595
bool SessionManagerPrivate::projectContainsFile(Project *p, const QString &fileName) const
596 597 598 599 600
{
    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
601 602
}

603
void SessionManager::configureEditor(Core::IEditor *editor, const QString &fileName)
con's avatar
con committed
604
{
605
    if (TextEditor::BaseTextEditor *textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
606 607
        Project *project = projectForFile(fileName);
        // Global settings are the default.
608 609
        if (project)
            project->editorConfiguration()->configureEditor(textEditor);
610
    }
con's avatar
con committed
611 612 613 614 615 616 617 618
}

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

    foreach (Project *pro, remove) {
        if (debug)
619
            qDebug() << "SessionManager - emitting aboutToRemoveProject(" << pro->displayName() << ")";
hjk's avatar
hjk committed
620
        emit m_instance->aboutToRemoveProject(pro);
con's avatar
con committed
621 622 623 624 625 626 627
    }


    // Refresh dependencies
    QSet<QString> projectFiles;
    foreach (Project *pro, projects()) {
        if (!remove.contains(pro))
628
            projectFiles.insert(pro->projectFilePath().toString());
con's avatar
con committed
629 630 631 632 633
    }

    QSet<QString>::const_iterator i = projectFiles.begin();
    while (i != projectFiles.end()) {
        QStringList dependencies;
hjk's avatar
hjk committed
634
        foreach (const QString &dependency, d->m_depMap.value(*i)) {
con's avatar
con committed
635 636 637 638 639 640 641 642
            if (projectFiles.contains(dependency))
                dependencies << dependency;
        }
        if (!dependencies.isEmpty())
            resMap.insert(*i, dependencies);
        ++i;
    }

hjk's avatar
hjk committed
643
    d->m_depMap = resMap;
con's avatar
con committed
644

645 646 647 648 649
    // TODO: Clear m_modelProjectHash

    // Delete projects
    foreach (Project *pro, remove) {
        pro->saveSettings();
hjk's avatar
hjk committed
650
        d->m_projects.removeOne(pro);
651

hjk's avatar
hjk committed
652
        if (pro == d->m_startupProject)
653 654
            setStartupProject(0);

hjk's avatar
hjk committed
655 656
        disconnect(pro, SIGNAL(fileListChanged()), m_instance, SLOT(clearProjectFileCache()));
        d->m_projectFileCache.remove(pro);
657 658 659

        if (debug)
            qDebug() << "SessionManager - emitting projectRemoved(" << pro->displayName() << ")";
hjk's avatar
hjk committed
660 661
        d->m_sessionNode->removeProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
        emit m_instance->projectRemoved(pro);
662 663 664
        delete pro;
    }

con's avatar
con committed
665
    if (startupProject() == 0)
hjk's avatar
hjk committed
666 667
        if (!d->m_projects.isEmpty())
            setStartupProject(d->m_projects.first());
con's avatar
con committed
668 669
}

670
/*!
671
    Lets other plugins store persistent values within the session file.
672 673
*/

con's avatar
con committed
674 675
void SessionManager::setValue(const QString &name, const QVariant &value)
{
hjk's avatar
hjk committed
676
    if (d->m_values.value(name) == value)
677
        return;
hjk's avatar
hjk committed
678
    d->m_values.insert(name, value);
679
    markSessionFileDirty(false);
con's avatar
con committed
680 681 682 683
}

QVariant SessionManager::value(const QString &name)
{
hjk's avatar
hjk committed
684 685
    QMap<QString, QVariant>::const_iterator it = d->m_values.find(name);
    return (it == d->m_values.constEnd()) ? QVariant() : *it;
con's avatar
con committed
686 687
}

hjk's avatar
hjk committed
688
QString SessionManager::activeSession()
con's avatar
con committed
689
{
hjk's avatar
hjk committed
690
    return d->m_sessionName;
con's avatar
con committed
691 692
}

hjk's avatar
hjk committed
693
QStringList SessionManager::sessions()
con's avatar
con committed
694
{
hjk's avatar
hjk committed
695
    if (d->m_sessions.isEmpty()) {
Tobias Hunger's avatar
Tobias Hunger committed
696
        // We are not initialized yet, so do that now
hjk's avatar
hjk committed
697
        QDir sessionDir(Core::ICore::userResourcePath());
698
        QList<QFileInfo> sessionFiles = sessionDir.entryInfoList(QStringList() << QLatin1String("*.qws"), QDir::NoFilter, QDir::Time);
hjk's avatar
hjk committed
699
        foreach (const QFileInfo &fileInfo, sessionFiles) {
700
            if (fileInfo.completeBaseName() != QLatin1String("default"))
hjk's avatar
hjk committed
701
                d->m_sessions << fileInfo.completeBaseName();
702
        }
hjk's avatar
hjk committed
703
        d->m_sessions.prepend(QLatin1String("default"));
704
    }
hjk's avatar
hjk committed
705
    return d->m_sessions;
con's avatar
con committed
706 707
}

hjk's avatar
hjk committed
708
FileName SessionManager::sessionNameToFileName(const QString &session)
con's avatar
con committed
709
{
hjk's avatar
hjk committed
710
    return FileName::fromString(ICore::userResourcePath() + QLatin1Char('/') + session + QLatin1String(".qws"));
con's avatar
con committed
711 712
}

713
/*!
714
    Creates \a session, but does not actually create the file.
715 716
*/

con's avatar
con committed
717 718 719 720
bool SessionManager::createSession(const QString &session)
{
    if (sessions().contains(session))
        return false;
hjk's avatar
hjk committed
721 722
    Q_ASSERT(d->m_sessions.size() > 0);
    d->m_sessions.insert(1, session);
con's avatar
con committed
723 724 725
    return true;
}

726 727 728 729 730 731 732 733 734
bool SessionManager::renameSession(const QString &original, const QString &newName)
{
    if (!cloneSession(original, newName))
        return false;
    if (original == activeSession())
        loadSession(newName);
    return deleteSession(original);
}

735 736 737 738 739 740 741 742 743 744 745 746

/*!
    \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;
}

747
/*!
748
     Deletes \a session name from session list and the file from disk.
749
*/
con's avatar
con committed
750 751
bool SessionManager::deleteSession(const QString &session)
{
hjk's avatar
hjk committed
752
    if (!d->m_sessions.contains(session))
con's avatar
con committed
753
        return false;
hjk's avatar
hjk committed
754
    d->m_sessions.removeOne(session);
755
    QFile fi(sessionNameToFileName(session).toString());
con's avatar
con committed
756 757 758 759 760 761 762
    if (fi.exists())
        return fi.remove();
    return false;
}

bool SessionManager::cloneSession(const QString &original, const QString &clone)
{
hjk's avatar
hjk committed
763
    if (!d->m_sessions.contains(original))
con's avatar
con committed
764 765
        return false;

766
    QFile fi(sessionNameToFileName(original).toString());
con's avatar
con committed
767
    // If the file does not exist, we can still clone
768
    if (!fi.exists() || fi.copy(sessionNameToFileName(clone).toString())) {
hjk's avatar
hjk committed
769 770
        Q_ASSERT(d->m_sessions.size() > 0);
        d->m_sessions.insert(1, clone);
con's avatar
con committed
771 772 773 774 775
        return true;
    }
    return false;
}

hjk's avatar
hjk committed
776
void SessionManagerPrivate::restoreValues(const PersistentSettingsReader &reader)
777
{
hjk's avatar
hjk committed
778
    const QStringList keys = reader.restoreValue(QLatin1String("valueKeys")).toStringList();
779 780 781 782 783 784
    foreach (const QString &key, keys) {
        QVariant value = reader.restoreValue(QLatin1String("value-") + key);
        m_values.insert(key, value);
    }
}

hjk's avatar
hjk committed
785
void SessionManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader)
786 787 788 789 790
{
    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();
791 792 793 794 795 796 797
        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);
798 799 800 801 802
        }
        ++i;
    }
}

hjk's avatar
hjk committed
803
void SessionManagerPrivate::askUserAboutFailedProjects()
804 805 806 807 808 809
{
    QStringList failedProjects = m_failedProjects;
    if (!failedProjects.isEmpty()) {
        QString fileList =
            QDir::toNativeSeparators(failedProjects.join(QLatin1String("<br>")));
        QMessageBox * box = new QMessageBox(QMessageBox::Warning,
hjk's avatar
hjk committed
810 811
                                            SessionManager::tr("Failed to restore project files"),
                                            SessionManager::tr("Could not restore the following project files:<br><b>%1</b>").
812
                                            arg(fileList));
hjk's avatar
hjk committed
813 814
        QPushButton * keepButton = new QPushButton(SessionManager::tr("Keep projects in Session"), box);
        QPushButton * removeButton = new QPushButton(SessionManager::tr("Remove projects from Session"), box);
815 816 817 818 819 820 821 822 823 824
        box->addButton(keepButton, QMessageBox::AcceptRole);
        box->addButton(removeButton, QMessageBox::DestructiveRole);

        box->exec();

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

hjk's avatar
hjk committed
825
void SessionManagerPrivate::restoreStartupProject(const PersistentSettingsReader &reader)
826 827 828
{
    const QString startupProject = reader.restoreValue(QLatin1String("StartupProject")).toString();
    if (!startupProject.isEmpty()) {
hjk's avatar
hjk committed
829
        foreach (Project *pro, d->m_projects) {
830
            if (pro->projectFilePath().toString() == startupProject) {
hjk's avatar
hjk committed
831
                m_instance->setStartupProject(pro);
832 833 834
                break;
            }
        }
835 836
    }
    if (!m_startupProject) {
837 838
        if (!startupProject.isEmpty())
            qWarning() << "Could not find startup project" << startupProject;
hjk's avatar
hjk committed
839 840
        if (!m_projects.isEmpty())
            m_instance->setStartupProject(m_projects.first());
841 842 843
    }
}

hjk's avatar
hjk committed
844
void SessionManagerPrivate::restoreEditors(const PersistentSettingsReader &reader)
845
{
hjk's avatar
hjk committed
846
    const QVariant editorsettings = reader.restoreValue(QLatin1String("EditorSettings"));
847
    if (editorsettings.isValid()) {
848
        EditorManager::restoreState(QByteArray::fromBase64(editorsettings.toByteArray()));
849
        sessionLoadingProgress();
850 851 852
    }
}

853
/*!
854
     Loads a session, takes a session name (not filename).
855
*/
hjk's avatar
hjk committed
856
void SessionManagerPrivate::restoreProjects(const QStringList &fileList)
857 858 859 860 861 862 863 864
{
    // 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())
hjk's avatar
hjk committed
865
            QMessageBox::critical(Core::ICore::mainWindow(), SessionManager::tr("Failed to open project"), errors);
866
        foreach (Project *p, projects)
867
            m_failedProjects.removeAll(p->projectFilePath().toString());
868 869
    }
}
870

con's avatar
con committed
871 872 873 874 875
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
hjk's avatar
hjk committed
876
    if (session == d->m_sessionName && !isDefaultVirgin())
con's avatar
con committed
877 878 879 880
        return true;

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

882
    // Try loading the file
hjk's avatar
hjk committed
883
    FileName fileName = sessionNameToFileName(session);
884
    PersistentSettingsReader reader;
885
    if (fileName.toFileInfo().exists()) {
886
        if (!reader.load(fileName)) {
887
            QMessageBox::warning(ICore::dialogParent(), tr("Error while restoring session"),
888
                                 tr("Could not restore session %1").arg(fileName.toUserOutput()));
889 890 891 892
            return false;
        }
    }

hjk's avatar
hjk committed
893
    d->m_loadingSession = true;
894

895
    // Allow everyone to set something in the session and before saving
hjk's avatar
hjk committed
896
    emit m_instance->aboutToUnloadSession(d->m_sessionName);
897 898

    if (!isDefaultVirgin()) {
899
        if (!save()) {
hjk's avatar
hjk committed
900
            d->m_loadingSession = false;
901
            return false;