session.cpp 36.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
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
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25 26 27

#include "session.h"

con's avatar
con committed
28
#include "project.h"
29 30 31 32
#include "target.h"
#include "kit.h"
#include "buildconfiguration.h"
#include "deployconfiguration.h"
33
#include "foldernavigationwidget.h"
con's avatar
con committed
34
#include "projectexplorer.h"
35
#include "projectnodes.h"
36
#include "editorconfiguration.h"
con's avatar
con committed
37 38

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

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

48
#include <utils/algorithm.h>
49
#include <utils/stylehelper.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

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

hjk's avatar
hjk committed
62 63
namespace ProjectExplorer {

64 65 66
/*!
     \class ProjectExplorer::SessionManager

67
     \brief The SessionManager class manages sessions.
68 69

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

hjk's avatar
hjk committed
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
class SessionManagerPrivate
{
public:
    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;

91
    static QString windowTitleAddition(const QString &filePath);
92
    static QString sessionTitle(const QString &filePath);
93

94 95
    bool hasProjects() const { return !m_projects.isEmpty(); }

96 97 98 99
    QString m_sessionName = QLatin1String("default");
    bool m_virginSession = true;
    bool m_loadingSession = false;
    bool m_casadeSetActive = false;
hjk's avatar
hjk committed
100 101

    mutable QStringList m_sessions;
102
    mutable QHash<QString, QDateTime> m_sessionDateTimes;
hjk's avatar
hjk committed
103 104 105

    mutable QHash<Project *, QStringList> m_projectFileCache;

106
    Project *m_startupProject = nullptr;
hjk's avatar
hjk committed
107
    QList<Project *> m_projects;
hjk's avatar
hjk committed
108 109 110 111
    QStringList m_failedProjects;
    QMap<QString, QStringList> m_depMap;
    QMap<QString, QVariant> m_values;
    QFutureInterface<void> m_future;
112
    PersistentSettingsWriter *m_writer = nullptr;
113 114 115

private:
    static QString locationInProject(const QString &filePath);
hjk's avatar
hjk committed
116 117
};

Tobias Hunger's avatar
Tobias Hunger committed
118 119
static SessionManager *m_instance = nullptr;
static SessionManagerPrivate *d = nullptr;
hjk's avatar
hjk committed
120

121 122
static QString projectFolderId(Project *pro)
{
123
    return pro->projectFilePath().toString();
124 125
}

126 127
const int PROJECT_SORT_VALUE = 100;

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

133 134
    connect(ModeManager::instance(), &ModeManager::currentModeChanged,
            this, &SessionManager::saveActiveMode);
135

136 137
    connect(EditorManager::instance(), &EditorManager::editorCreated,
            this, &SessionManager::configureEditor);
138 139 140 141 142 143 144
    connect(this, &SessionManager::projectAdded,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(this, &SessionManager::projectRemoved,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(this, &SessionManager::projectDisplayNameChanged,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(EditorManager::instance(), &EditorManager::editorOpened,
145
            this, [] { markSessionFileDirty(); });
146
    connect(EditorManager::instance(), &EditorManager::editorsClosed,
147
            this, [] { markSessionFileDirty(); });
148 149

    EditorManager::setWindowTitleAdditionHandler(&SessionManagerPrivate::windowTitleAddition);
150
    EditorManager::setSessionTitleHandler(&SessionManagerPrivate::sessionTitle);
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
}

160
SessionManager *SessionManager::instance()
hjk's avatar
hjk committed
161 162 163
{
   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
}

hjk's avatar
hjk committed
175
void SessionManager::saveActiveMode(Id mode)
con's avatar
con committed
176
{
hjk's avatar
hjk committed
177 178
    if (mode != Core::Constants::MODE_WELCOME)
        setValue(QLatin1String("ActiveMode"), mode.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
Tobias Hunger's avatar
Tobias Hunger committed
185
    auto 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;

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

    return true;
}

205
/*
206
 * The dependency management exposes an interface based on projects, but
207 208 209 210 211
 * 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
212
QList<Project *> SessionManager::dependencies(const Project *project)
213
{
214
    const QString proName = project->projectFilePath().toString();
hjk's avatar
hjk committed
215
    const QStringList proDeps = d->m_depMap.value(proName);
216 217 218

    QList<Project *> projects;
    foreach (const QString &dep, proDeps) {
219
        if (Project *pro = projectForFile(Utils::FileName::fromString(dep)))
220 221 222 223 224 225
            projects += pro;
    }

    return projects;
}

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

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

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

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

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

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

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

    return true;
}

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

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

276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
bool SessionManager::isProjectConfigurationCascading()
{
    return d->m_casadeSetActive;
}

void SessionManager::setProjectConfigurationCascading(bool b)
{
    d->m_casadeSetActive = b;
    markSessionFileDirty();
}

void SessionManager::setActiveTarget(Project *project, Target *target, SetActive cascade)
{
    QTC_ASSERT(project, return);

    project->setActiveTarget(target);

    if (!target) // never cascade setting no target
        return;

    if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
        return;

    Core::Id kitId = target->kit()->id();
300
    for (Project *otherProject : SessionManager::projects()) {
301 302
        if (otherProject == project)
            continue;
Tobias Hunger's avatar
Tobias Hunger committed
303 304 305
        if (Target *otherTarget = Utils::findOrDefault(otherProject->targets(),
                                                       [kitId](Target *t) { return t->kit()->id() == kitId; }))
            otherProject->setActiveTarget(otherTarget);
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
    }
}

void SessionManager::setActiveBuildConfiguration(Target *target, BuildConfiguration *bc, SetActive cascade)
{
    QTC_ASSERT(target, return);
    target->setActiveBuildConfiguration(bc);

    if (!bc)
        return;
    if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
        return;

    Core::Id kitId = target->kit()->id();
    QString name = bc->displayName(); // We match on displayname
321
    for (Project *otherProject : SessionManager::projects()) {
322 323 324
        if (otherProject == target->project())
            continue;
        Target *otherTarget = otherProject->activeTarget();
325
        if (!otherTarget || otherTarget->kit()->id() != kitId)
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
            continue;

        foreach (BuildConfiguration *otherBc, otherTarget->buildConfigurations()) {
            if (otherBc->displayName() == name) {
                otherTarget->setActiveBuildConfiguration(otherBc);
                break;
            }
        }
    }
}

void SessionManager::setActiveDeployConfiguration(Target *target, DeployConfiguration *dc, SetActive cascade)
{
    QTC_ASSERT(target, return);
    target->setActiveDeployConfiguration(dc);

    if (!dc)
        return;
    if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
        return;

    Core::Id kitId = target->kit()->id();
    QString name = dc->displayName(); // We match on displayname
349
    for (Project *otherProject : SessionManager::projects()) {
350 351 352
        if (otherProject == target->project())
            continue;
        Target *otherTarget = otherProject->activeTarget();
353
        if (!otherTarget || otherTarget->kit()->id() != kitId)
354 355 356 357 358 359 360 361 362 363 364
            continue;

        foreach (DeployConfiguration *otherDc, otherTarget->deployConfigurations()) {
            if (otherDc->displayName() == name) {
                otherTarget->setActiveDeployConfiguration(otherDc);
                break;
            }
        }
    }
}

con's avatar
con committed
365 366
void SessionManager::setStartupProject(Project *startupProject)
{
367
    QTC_ASSERT((!startupProject && d->m_projects.isEmpty())
hjk's avatar
hjk committed
368
               || (startupProject && d->m_projects.contains(startupProject)), return);
con's avatar
con committed
369

hjk's avatar
hjk committed
370
    if (d->m_startupProject == startupProject)
371 372
        return;

hjk's avatar
hjk committed
373 374
    d->m_startupProject = startupProject;
    emit m_instance->startupProjectChanged(startupProject);
con's avatar
con committed
375 376
}

hjk's avatar
hjk committed
377
Project *SessionManager::startupProject()
con's avatar
con committed
378
{
hjk's avatar
hjk committed
379
    return d->m_startupProject;
con's avatar
con committed
380 381
}

382
void SessionManager::addProject(Project *pro)
con's avatar
con committed
383
{
384
    QTC_ASSERT(pro, return);
con's avatar
con committed
385

hjk's avatar
hjk committed
386
    d->m_virginSession = false;
hjk's avatar
hjk committed
387
    QTC_ASSERT(!d->m_projects.contains(pro), return);
con's avatar
con committed
388

hjk's avatar
hjk committed
389
    d->m_projects.append(pro);
390

391
    connect(pro, &Project::fileListChanged, m_instance, &SessionManager::clearProjectFileCache);
392 393
    connect(pro, &Project::displayNameChanged,
            m_instance, [pro]() { m_instance->projectDisplayNameChanged(pro); });
394 395

    emit m_instance->projectAdded(pro);
396
    const auto updateFolderNavigation = [pro] {
397
        const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon();
398 399 400
        FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro),
                                                            PROJECT_SORT_VALUE,
                                                            pro->displayName(),
401 402
                                                            pro->projectFilePath().parentDir(),
                                                            icon});
403 404
    };
    updateFolderNavigation();
405
    configureEditors(pro);
406 407 408 409
    connect(pro, &Project::fileListChanged, [pro, updateFolderNavigation]() {
        configureEditors(pro);
        updateFolderNavigation(); // update icon
    });
410
    connect(pro, &Project::displayNameChanged, pro, updateFolderNavigation);
con's avatar
con committed
411 412 413 414
}

void SessionManager::removeProject(Project *project)
{
hjk's avatar
hjk committed
415
    d->m_virginSession = false;
Tobias Hunger's avatar
Tobias Hunger committed
416
    QTC_ASSERT(project, return);
417
    removeProjects({project});
con's avatar
con committed
418 419
}

420 421
bool SessionManager::loadingSession()
{
hjk's avatar
hjk committed
422
    return d->m_loadingSession;
423 424
}

con's avatar
con committed
425 426
bool SessionManager::save()
{
427 428 429 430
    // do not save new virgin default sessions
    if (isDefaultVirgin())
        return true;

hjk's avatar
hjk committed
431
    emit m_instance->aboutToSaveSession();
con's avatar
con committed
432

hjk's avatar
hjk committed
433 434 435
    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
436 437
                                                       QLatin1String("QtCreatorSession"));
    }
438

439
    QVariantMap data;
440
    // save the startup project
hjk's avatar
hjk committed
441
    if (d->m_startupProject)
442
        data.insert(QLatin1String("StartupProject"), d->m_startupProject->projectFilePath().toString());
443

hjk's avatar
hjk committed
444
    QColor c = StyleHelper::requestedBaseColor();
445 446 447 448 449
    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'));
450
        data.insert(QLatin1String("Color"), tmp);
451 452
    }

453 454
    QStringList projectFiles
            = Utils::transform(projects(), [](Project *p) { return p->projectFilePath().toString(); });
455
    // Restore infromation on projects that failed to load:
456
    // don't readd projects to the list, which the user loaded
Tobias Hunger's avatar
Tobias Hunger committed
457
    foreach (const QString &failed, d->m_failedProjects) {
458 459
        if (!projectFiles.contains(failed))
            projectFiles << failed;
Tobias Hunger's avatar
Tobias Hunger committed
460
    }
461

462
    data.insert(QLatin1String("ProjectList"), projectFiles);
463
    data.insert(QLatin1String("CascadeSetActive"), d->m_casadeSetActive);
464 465

    QMap<QString, QVariant> depMap;
466
    auto i = d->m_depMap.constBegin();
hjk's avatar
hjk committed
467
    while (i != d->m_depMap.constEnd()) {
468 469
        QString key = i.key();
        QStringList values;
Tobias Hunger's avatar
Tobias Hunger committed
470
        foreach (const QString &value, i.value())
471 472 473 474
            values << value;
        depMap.insert(key, values);
        ++i;
    }
475
    data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
476
    data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
477

478
    auto end = d->m_values.constEnd();
479
    QStringList keys;
480
    for (auto it = d->m_values.constBegin(); it != end; ++it) {
481
        data.insert(QLatin1String("value-") + it.key(), it.value());
482 483 484
        keys << it.key();
    }

485
    data.insert(QLatin1String("valueKeys"), keys);
486

487
    bool result = d->m_writer->save(data, ICore::mainWindow());
488 489 490
    if (result) {
        d->m_sessionDateTimes.insert(activeSession(), QDateTime::currentDateTime());
    } else {
491
        QMessageBox::warning(ICore::dialogParent(), tr("Error while saving session"),
hjk's avatar
hjk committed
492
            tr("Could not save session to file %1").arg(d->m_writer->fileName().toUserOutput()));
con's avatar
con committed
493 494 495 496 497 498
    }

    return result;
}

/*!
499
  Closes all projects
con's avatar
con committed
500
  */
501
void SessionManager::closeAllProjects()
con's avatar
con committed
502
{
503
    removeProjects(projects());
con's avatar
con committed
504 505
}

506
const QList<Project *> SessionManager::projects()
hjk's avatar
hjk committed
507
{
hjk's avatar
hjk committed
508
    return d->m_projects;
hjk's avatar
hjk committed
509 510 511
}

bool SessionManager::hasProjects()
con's avatar
con committed
512
{
513 514 515 516 517
    return d->hasProjects();
}

bool SessionManager::hasProject(Project *p)
{
hjk's avatar
hjk committed
518
    return d->m_projects.contains(p);
con's avatar
con committed
519 520
}

hjk's avatar
hjk committed
521
QStringList SessionManagerPrivate::dependencies(const QString &proName) const
con's avatar
con committed
522 523
{
    QStringList result;
524
    dependencies(proName, result);
con's avatar
con committed
525 526 527
    return result;
}

hjk's avatar
hjk committed
528
void SessionManagerPrivate::dependencies(const QString &proName, QStringList &result) const
529 530 531 532 533 534 535 536 537 538
{
    QStringList depends = m_depMap.value(proName);

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

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

539
QString SessionManagerPrivate::sessionTitle(const QString &filePath)
540 541 542 543
{
    if (SessionManager::isDefaultSession(d->m_sessionName)) {
        if (filePath.isEmpty()) {
            // use single project's name if there is only one loaded.
544
            const QList<Project *> projects = SessionManager::projects();
545 546 547 548 549 550 551 552 553
            if (projects.size() == 1)
                return projects.first()->displayName();
        }
    } else {
        QString sessionName = d->m_sessionName;
        if (sessionName.isEmpty())
            sessionName = SessionManager::tr("Untitled");
        return sessionName;
    }
554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
    return QString();
}

QString SessionManagerPrivate::locationInProject(const QString &filePath) {
    Project *project = SessionManager::projectForFile(Utils::FileName::fromString(filePath));
    if (!project)
        return QString();

    Utils::FileName file = Utils::FileName::fromString(filePath);
    Utils::FileName parentDir = file.parentDir();
    if (parentDir == project->projectDirectory())
        return "@ " + project->displayName();

    if (file.isChildOf(project->projectDirectory())) {
        Utils::FileName dirInProject = parentDir.relativeChildPath(project->projectDirectory());
        return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")";
    }

    // For a file that is "outside" the project it belongs to, we display its
    // dir's full path because it is easier to read than a series of  "../../.".
    // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp
   return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")";
}

QString SessionManagerPrivate::windowTitleAddition(const QString &filePath)
{
    return locationInProject(filePath);
581 582
}

hjk's avatar
hjk committed
583
QStringList SessionManagerPrivate::dependenciesOrder() const
con's avatar
con committed
584 585 586 587 588
{
    QList<QPair<QString, QStringList> > unordered;
    QStringList ordered;

    // copy the map to a temporary list
hjk's avatar
hjk committed
589 590
    for (const Project *pro : m_projects) {
        const QString proName = pro->projectFilePath().toString();
hjk's avatar
hjk committed
591
        unordered << QPair<QString, QStringList>(proName, m_depMap.value(proName));
con's avatar
con committed
592 593 594
    }

    while (!unordered.isEmpty()) {
595
        for (int i = (unordered.count() - 1); i >= 0; --i) {
con's avatar
con committed
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615
            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;
}

616
QList<Project *> SessionManager::projectOrder(const Project *project)
con's avatar
con committed
617 618 619 620
{
    QList<Project *> result;

    QStringList pros;
621
    if (project)
622
        pros = d->dependencies(project->projectFilePath().toString());
623
    else
hjk's avatar
hjk committed
624
        pros = d->dependenciesOrder();
con's avatar
con committed
625 626

    foreach (const QString &proFile, pros) {
627
        for (Project *pro : projects()) {
628
            if (pro->projectFilePath().toString() == proFile) {
con's avatar
con committed
629 630 631 632 633 634 635 636 637
                result << pro;
                break;
            }
        }
    }

    return result;
}

638 639
// node for file returns a randomly selected node if there are multiple
// prefer to use nodesForFile and figure out which node you want
640
Node *SessionManager::nodeForFile(const Utils::FileName &fileName)
641
{
Tobias Hunger's avatar
Tobias Hunger committed
642
    Node *node = nullptr;
hjk's avatar
hjk committed
643 644 645 646 647 648 649 650 651
    for (Project *project : d->m_projects) {
        if (ProjectNode *projectNode = project->rootProjectNode()) {
            projectNode->forEachGenericNode([&](Node *n) {
                if (n->filePath() == fileName) {
                    // prefer file nodes
                    if (!node || (node->nodeType() != NodeType::File && n->nodeType() == NodeType::File))
                        node = n;
                }
            });
652
        }
hjk's avatar
hjk committed
653
    }
654 655 656
    return node;
}

hjk's avatar
hjk committed
657
Project *SessionManager::projectForNode(Node *node)
con's avatar
con committed
658 659
{
    if (!node)
Tobias Hunger's avatar
Tobias Hunger committed
660
        return nullptr;
con's avatar
con committed
661

hjk's avatar
hjk committed
662 663 664
    FolderNode *folder = node->asFolderNode();
    if (!folder)
        folder = node->parentFolderNode();
hjk's avatar
hjk committed
665

hjk's avatar
hjk committed
666 667
    while (folder && folder->parentFolderNode())
        folder = folder->parentFolderNode();
con's avatar
con committed
668

hjk's avatar
hjk committed
669 670 671
    for (Project *pro : d->m_projects) {
        if (pro->containerNode() == folder)
            return pro;
672 673 674 675
    }
    return nullptr;
}

676
Project *SessionManager::projectForFile(const Utils::FileName &fileName)
con's avatar
con committed
677
{
678
    const QList<Project *> &projectList = projects();
Tobias Hunger's avatar
Tobias Hunger committed
679
    foreach (Project *p, projectList) {
680
        if (projectContainsFile(p, fileName))
681
            return p;
Tobias Hunger's avatar
Tobias Hunger committed
682
    }
hjk's avatar
hjk committed
683

Tobias Hunger's avatar
Tobias Hunger committed
684
    return nullptr;
685 686
}

687
bool SessionManager::projectContainsFile(Project *p, const Utils::FileName &fileName)
688
{
689 690
    if (!d->m_projectFileCache.contains(p))
        d->m_projectFileCache.insert(p, p->files(Project::AllFiles));
691

692
    return d->m_projectFileCache.value(p).contains(fileName.toString());
con's avatar
con committed
693 694
}

695
void SessionManager::configureEditor(IEditor *editor, const QString &fileName)
con's avatar
con committed
696
{
Tobias Hunger's avatar
Tobias Hunger committed
697
    if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
698
        Project *project = projectForFile(Utils::FileName::fromString(fileName));
699
        // Global settings are the default.
700 701
        if (project)
            project->editorConfiguration()->configureEditor(textEditor);
702
    }
con's avatar
con committed
703 704
}

705 706 707
void SessionManager::configureEditors(Project *project)
{
    foreach (IDocument *document, DocumentModel::openedDocuments()) {
708
        if (projectContainsFile(project, document->filePath())) {
709
            foreach (IEditor *editor, DocumentModel::editorsForDocument(document)) {
Tobias Hunger's avatar
Tobias Hunger committed
710
                if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
711 712 713 714 715 716 717
                        project->editorConfiguration()->configureEditor(textEditor);
                }
            }
        }
    }
}

718
void SessionManager::removeProjects(const QList<Project *> &remove)
con's avatar
con committed
719 720 721
{
    QMap<QString, QStringList> resMap;

722
    for (Project *pro : remove)
hjk's avatar
hjk committed
723
        emit m_instance->aboutToRemoveProject(pro);
con's avatar
con committed
724 725 726

    // Refresh dependencies
    QSet<QString> projectFiles;
727
    for (Project *pro : projects()) {
con's avatar
con committed
728
        if (!remove.contains(pro))
729
            projectFiles.insert(pro->projectFilePath().toString());
con's avatar
con committed
730 731
    }

732
    auto  i = projectFiles.begin();
con's avatar
con committed
733 734
    while (i != projectFiles.end()) {
        QStringList dependencies;
hjk's avatar
hjk committed
735
        foreach (const QString &dependency, d->m_depMap.value(*i)) {
con's avatar
con committed
736 737 738 739 740 741 742 743
            if (projectFiles.contains(dependency))
                dependencies << dependency;
        }
        if (!dependencies.isEmpty())
            resMap.insert(*i, dependencies);
        ++i;
    }

hjk's avatar
hjk committed
744
    d->m_depMap = resMap;
745
    bool changeStartupProject = false;
746 747

    // Delete projects
748
    for (Project *pro : remove) {
749
        pro->saveSettings();
750

751
        // Remove the project node:
hjk's avatar
hjk committed
752
        d->m_projects.removeOne(pro);
753

hjk's avatar
hjk committed
754
        if (pro == d->m_startupProject)
755
            changeStartupProject = true;
756

757 758
        disconnect(pro, &Project::fileListChanged,
                   m_instance, &SessionManager::clearProjectFileCache);
hjk's avatar
hjk committed
759 760
        d->m_projectFileCache.remove(pro);
        emit m_instance->projectRemoved(pro);
761
        FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro));
762 763
    }

764 765
    if (changeStartupProject)
        setStartupProject(hasProjects() ? projects().first() : nullptr);
766 767

     qDeleteAll(remove);
con's avatar
con committed
768 769
}

770
/*!
771
    Lets other plugins store persistent values within the session file.
772 773
*/

con's avatar
con committed
774 775
void SessionManager::setValue(const QString &name, const QVariant &value)
{
hjk's avatar
hjk committed
776
    if (d->m_values.value(name) == value)
777
        return;
hjk's avatar
hjk committed
778
    d->m_values.insert(name, value);
779
    markSessionFileDirty(false);
con's avatar
con committed
780 781 782 783
}

QVariant SessionManager::value(const QString &name)
{
784
    auto it = d->m_values.constFind(name);
hjk's avatar
hjk committed
785
    return (it == d->m_values.constEnd()) ? QVariant() : *it;
con's avatar
con committed
786 787
}

hjk's avatar
hjk committed
788
QString SessionManager::activeSession()
con's avatar
con committed
789
{
hjk's avatar
hjk committed
790
    return d->m_sessionName;
con's avatar
con committed
791 792
}

hjk's avatar
hjk committed
793
QStringList SessionManager::sessions()
con's avatar
con committed
794
{
hjk's avatar
hjk committed
795
    if (d->m_sessions.isEmpty()) {
Tobias Hunger's avatar
Tobias Hunger committed
796
        // We are not initialized yet, so do that now
797
        QDir sessionDir(ICore::userResourcePath());
798
        QList<QFileInfo> sessionFiles = sessionDir.entryInfoList(QStringList() << QLatin1String("*.qws"), QDir::NoFilter, QDir::Time);
hjk's avatar
hjk committed
799
        foreach (const QFileInfo &fileInfo, sessionFiles) {
800 801 802 803
            const QString &name = fileInfo.completeBaseName();
            d->m_sessionDateTimes.insert(name, fileInfo.lastModified());
            if (name != QLatin1String("default"))
                d->m_sessions << name;
804
        }
hjk's avatar
hjk committed
805
        d->m_sessions.prepend(QLatin1String("default"));
806
    }
hjk's avatar
hjk committed
807
    return d->m_sessions;
con's avatar
con committed
808 809
}

810 811 812 813 814
QDateTime SessionManager::sessionDateTime(const QString &session)
{
    return d->m_sessionDateTimes.value(session);
}

hjk's avatar
hjk committed
815
FileName SessionManager::sessionNameToFileName(const QString &session)
con's avatar
con committed
816
{
hjk's avatar
hjk committed
817
    return FileName::fromString(ICore::userResourcePath() + QLatin1Char('/') + session + QLatin1String(".qws"));
con's avatar
con committed
818 819
}

820
/*!
821
    Creates \a session, but does not actually create the file.
822 823
*/

con's avatar
con committed
824 825 826 827
bool SessionManager::createSession(const QString &session)
{
    if (sessions().contains(session))
        return false;
hjk's avatar
hjk committed
828 829
    Q_ASSERT(d->m_sessions.size() > 0);
    d->m_sessions.insert(1, session);
con's avatar
con committed
830 831 832
    return true;
}

833 834 835 836 837 838 839 840 841
bool SessionManager::renameSession(const QString &original, const QString &newName)
{
    if (!cloneSession(original, newName))
        return false;
    if (original == activeSession())
        loadSession(newName);
    return deleteSession(original);
}

842 843 844 845 846 847

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

854
/*!
855
     Deletes \a session name from session list and the file from disk.
856
*/
con's avatar
con committed
857 858
bool SessionManager::deleteSession(const QString &session)
{
hjk's avatar
hjk committed
859
    if (!d->m_sessions.contains(session))
con's avatar
con committed
860
        return false;
hjk's avatar
hjk committed
861
    d->m_sessions.removeOne(session);
862
    QFile fi(sessionNameToFileName(session).toString());
con's avatar
con committed
863 864 865 866 867 868 869
    if (fi.exists())
        return fi.remove();
    return false;
}

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

873
    QFile fi(sessionNameToFileName(original).toString());
con's avatar
con committed
874
    // If the file does not exist, we can still clone
875
    if (!fi.exists() || fi.copy(sessionNameToFileName(clone).toString())) {
hjk's avatar
hjk committed
876
        d->m_sessions.insert(1, clone);
877
        d->m_sessionDateTimes.insert(clone, sessionNameToFileName(clone).toFileInfo().lastModified());
con's avatar
con committed
878 879 880 881 882
        return true;
    }
    return false;
}

hjk's avatar
hjk committed
883
void SessionManagerPrivate::restoreValues(const PersistentSettingsReader &reader)
884
{
hjk's avatar
hjk committed
885
    const QStringList keys = reader.restoreValue(QLatin1String("valueKeys")).toStringList();
886 887 888 889 890 891
    foreach (const QString &key, keys) {
        QVariant value = reader.restoreValue(QLatin1String("value-") + key);
        m_values.insert(key, value);
    }
}

hjk's avatar
hjk committed
892
void SessionManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader)
893 894
{
    QMap<QString, QVariant> depMap = reader.restoreValue(QLatin1String("ProjectDependencies")).toMap();
895
    auto i = depMap.constBegin();