session.cpp 36.3 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30 31 32

#include "session.h"

con's avatar
con committed
33
#include "project.h"
34 35 36 37
#include "target.h"
#include "kit.h"
#include "buildconfiguration.h"
#include "deployconfiguration.h"
con's avatar
con committed
38 39 40
#include "projectexplorer.h"
#include "nodesvisitor.h"
#include "editorconfiguration.h"
41
#include "projectnodes.h"
con's avatar
con committed
42 43

#include <coreplugin/icore.h>
44
#include <coreplugin/idocument.h>
con's avatar
con committed
45 46
#include <coreplugin/imode.h>
#include <coreplugin/editormanager/editormanager.h>
47
#include <coreplugin/coreconstants.h>
48
#include <coreplugin/progressmanager/progressmanager.h>
con's avatar
con committed
49 50
#include <coreplugin/modemanager.h>

51
#include <texteditor/texteditor.h>
con's avatar
con committed
52

53
#include <utils/algorithm.h>
54
#include <utils/stylehelper.h>
hjk's avatar
hjk committed
55

56 57 58
#include <QDebug>
#include <QDir>
#include <QFileInfo>
hjk's avatar
hjk committed
59

60 61
#include <QMessageBox>
#include <QPushButton>
con's avatar
con committed
62

hjk's avatar
hjk committed
63
namespace { bool debug = false; }
con's avatar
con committed
64

65
using namespace Core;
hjk's avatar
hjk committed
66
using namespace Utils;
67
using namespace ProjectExplorer::Internal;
con's avatar
con committed
68

hjk's avatar
hjk committed
69 70
namespace ProjectExplorer {

71 72 73
/*!
     \class ProjectExplorer::SessionManager

74
     \brief The SessionManager class manages sessions.
75 76

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

hjk's avatar
hjk committed
82 83 84 85 86 87 88 89 90 91 92
class SessionManagerPrivate
{
public:
    SessionManagerPrivate() :
        m_sessionName(QLatin1String("default")),
        m_virginSession(true),
        m_loadingSession(false),
        m_startupProject(0),
        m_writer(0)
    {}

93
    bool projectContainsFile(Project *p, const FileName &fileName) const;
hjk's avatar
hjk committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107
    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:
108 109
    static QString windowTitleAddition(const QString &filePath);

hjk's avatar
hjk committed
110 111 112 113 114 115 116 117 118
    SessionNode *m_sessionNode;
    QString m_sessionName;
    bool m_virginSession;

    mutable QStringList m_sessions;

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

119
    bool m_casadeSetActive;
hjk's avatar
hjk committed
120 121 122 123 124 125 126 127 128 129 130 131
    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
132
SessionManager::SessionManager(QObject *parent)
hjk's avatar
hjk committed
133
  : QObject(parent)
con's avatar
con committed
134
{
hjk's avatar
hjk committed
135 136 137
    m_instance = this;
    d = new SessionManagerPrivate;

138
    d->m_sessionNode = new SessionNode;
hjk's avatar
hjk committed
139

140 141
    connect(ModeManager::instance(), &ModeManager::currentModeChanged,
            this, &SessionManager::saveActiveMode);
142

143 144
    connect(EditorManager::instance(), &EditorManager::editorCreated,
            this, &SessionManager::configureEditor);
145 146 147 148 149 150 151 152 153 154
    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,
            [this] { markSessionFileDirty(); });
    connect(EditorManager::instance(), &EditorManager::editorsClosed,
            [this] { markSessionFileDirty(); });
155 156

    EditorManager::setWindowTitleAdditionHandler(&SessionManagerPrivate::windowTitleAddition);
hjk's avatar
hjk committed
157
}
con's avatar
con committed
158 159 160

SessionManager::~SessionManager()
{
hjk's avatar
hjk committed
161 162
    emit m_instance->aboutToUnloadSession(d->m_sessionName);
    delete d->m_writer;
163
    delete d->m_sessionNode;
hjk's avatar
hjk committed
164
    delete d;
con's avatar
con committed
165 166
}

167
SessionManager *SessionManager::instance()
hjk's avatar
hjk committed
168 169 170
{
   return m_instance;
}
con's avatar
con committed
171

hjk's avatar
hjk committed
172
bool SessionManager::isDefaultVirgin()
con's avatar
con committed
173
{
hjk's avatar
hjk committed
174
    return isDefaultSession(d->m_sessionName) && d->m_virginSession;
con's avatar
con committed
175 176
}

hjk's avatar
hjk committed
177
bool SessionManager::isDefaultSession(const QString &session)
con's avatar
con committed
178
{
hjk's avatar
hjk committed
179
    return session == QLatin1String("default");
con's avatar
con committed
180 181 182
}


183
void SessionManager::saveActiveMode(IMode *mode)
con's avatar
con committed
184
{
185 186
    if (mode->id() != Id(Core::Constants::MODE_WELCOME))
        setValue(QLatin1String("ActiveMode"), mode->id().toString());
con's avatar
con committed
187 188 189 190 191 192
}

void SessionManager::clearProjectFileCache()
{
    // If triggered by the fileListChanged signal of one project
    // only invalidate cache for this project
hjk's avatar
hjk committed
193
    Project *pro = qobject_cast<Project*>(m_instance->sender());
con's avatar
con committed
194
    if (pro)
hjk's avatar
hjk committed
195
        d->m_projectFileCache.remove(pro);
con's avatar
con committed
196
    else
hjk's avatar
hjk committed
197
        d->m_projectFileCache.clear();
con's avatar
con committed
198 199
}

hjk's avatar
hjk committed
200
bool SessionManagerPrivate::recursiveDependencyCheck(const QString &newDep, const QString &checkDep) const
con's avatar
con committed
201 202 203 204
{
    if (newDep == checkDep)
        return false;

hjk's avatar
hjk committed
205
    foreach (const QString &dependency, m_depMap.value(checkDep))
con's avatar
con committed
206 207 208 209 210 211
        if (!recursiveDependencyCheck(newDep, dependency))
            return false;

    return true;
}

212
/*
213
 * The dependency management exposes an interface based on projects, but
214 215 216 217 218
 * 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
219
QList<Project *> SessionManager::dependencies(const Project *project)
220
{
221
    const QString proName = project->projectFilePath().toString();
hjk's avatar
hjk committed
222
    const QStringList proDeps = d->m_depMap.value(proName);
223 224 225

    QList<Project *> projects;
    foreach (const QString &dep, proDeps) {
226
        if (Project *pro = projectForFile(Utils::FileName::fromString(dep)))
227 228 229 230 231 232
            projects += pro;
    }

    return projects;
}

hjk's avatar
hjk committed
233
bool SessionManager::hasDependency(const Project *project, const Project *depProject)
con's avatar
con committed
234
{
235 236
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
con's avatar
con committed
237

hjk's avatar
hjk committed
238
    const QStringList proDeps = d->m_depMap.value(proName);
con's avatar
con committed
239 240 241
    return proDeps.contains(depName);
}

hjk's avatar
hjk committed
242
bool SessionManager::canAddDependency(const Project *project, const Project *depProject)
con's avatar
con committed
243
{
244 245
    const QString newDep = project->projectFilePath().toString();
    const QString checkDep = depProject->projectFilePath().toString();
con's avatar
con committed
246

hjk's avatar
hjk committed
247
    return d->recursiveDependencyCheck(newDep, checkDep);
con's avatar
con committed
248 249
}

dt's avatar
dt committed
250
bool SessionManager::addDependency(Project *project, Project *depProject)
con's avatar
con committed
251
{
252 253
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
con's avatar
con committed
254 255

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

hjk's avatar
hjk committed
259
    QStringList proDeps = d->m_depMap.value(proName);
con's avatar
con committed
260 261
    if (!proDeps.contains(depName)) {
        proDeps.append(depName);
hjk's avatar
hjk committed
262
        d->m_depMap[proName] = proDeps;
con's avatar
con committed
263
    }
hjk's avatar
hjk committed
264
    emit m_instance->dependencyChanged(project, depProject);
con's avatar
con committed
265 266 267 268

    return true;
}

dt's avatar
dt committed
269
void SessionManager::removeDependency(Project *project, Project *depProject)
270
{
271 272
    const QString proName = project->projectFilePath().toString();
    const QString depName = depProject->projectFilePath().toString();
273

hjk's avatar
hjk committed
274
    QStringList proDeps = d->m_depMap.value(proName);
275
    proDeps.removeAll(depName);
276
    if (proDeps.isEmpty())
hjk's avatar
hjk committed
277
        d->m_depMap.remove(proName);
278
    else
hjk's avatar
hjk committed
279 280
        d->m_depMap[proName] = proDeps;
    emit m_instance->dependencyChanged(project, depProject);
281 282
}

283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
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();
    foreach (Project *otherProject, SessionManager::projects()) {
        if (otherProject == project)
            continue;
        foreach (Target *otherTarget, otherProject->targets()) {
            if (otherTarget->kit()->id() == kitId) {
                otherProject->setActiveTarget(otherTarget);
                break;
            }
        }
    }
}

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
    foreach (Project *otherProject, SessionManager::projects()) {
        if (otherProject == target->project())
            continue;
        Target *otherTarget = otherProject->activeTarget();
        if (otherTarget->kit()->id() != kitId)
            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
    foreach (Project *otherProject, SessionManager::projects()) {
        if (otherProject == target->project())
            continue;
        Target *otherTarget = otherProject->activeTarget();
        if (otherTarget->kit()->id() != kitId)
            continue;

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

con's avatar
con committed
375 376 377
void SessionManager::setStartupProject(Project *startupProject)
{
    if (debug)
378
        qDebug() << Q_FUNC_INFO << (startupProject ? startupProject->displayName() : QLatin1String("0"));
con's avatar
con committed
379 380

    if (startupProject) {
hjk's avatar
hjk committed
381
        Q_ASSERT(d->m_projects.contains(startupProject));
con's avatar
con committed
382 383
    }

hjk's avatar
hjk committed
384
    if (d->m_startupProject == startupProject)
385 386
        return;

hjk's avatar
hjk committed
387 388
    d->m_startupProject = startupProject;
    emit m_instance->startupProjectChanged(startupProject);
con's avatar
con committed
389 390
}

hjk's avatar
hjk committed
391
Project *SessionManager::startupProject()
con's avatar
con committed
392
{
hjk's avatar
hjk committed
393
    return d->m_startupProject;
con's avatar
con committed
394 395 396 397 398 399 400 401 402
}

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

void SessionManager::addProjects(const QList<Project*> &projects)
{
hjk's avatar
hjk committed
403
    d->m_virginSession = false;
con's avatar
con committed
404 405
    QList<Project*> clearedList;
    foreach (Project *pro, projects) {
hjk's avatar
hjk committed
406
        if (!d->m_projects.contains(pro)) {
con's avatar
con committed
407
            clearedList.append(pro);
hjk's avatar
hjk committed
408 409
            d->m_projects.append(pro);
            d->m_sessionNode->addProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
con's avatar
con committed
410

411 412
            connect(pro, &Project::fileListChanged,
                    m_instance, &SessionManager::clearProjectFileCache);
con's avatar
con committed
413

414 415
            connect(pro, &Project::displayNameChanged,
                    m_instance, &SessionManager::handleProjectDisplayNameChanged);
416

con's avatar
con committed
417
            if (debug)
418
                qDebug() << "SessionManager - adding project " << pro->displayName();
con's avatar
con committed
419 420 421
        }
    }

422
    foreach (Project *pro, clearedList) {
hjk's avatar
hjk committed
423
        emit m_instance->projectAdded(pro);
424 425 426 427 428 429
        configureEditors(pro);
        connect(pro, &Project::fileListChanged,
                [pro](){
                    configureEditors(pro);
                });
    }
con's avatar
con committed
430 431

    if (clearedList.count() == 1)
hjk's avatar
hjk committed
432
        emit m_instance->singleProjectAdded(clearedList.first());
con's avatar
con committed
433 434 435 436
}

void SessionManager::removeProject(Project *project)
{
hjk's avatar
hjk committed
437
    d->m_virginSession = false;
con's avatar
con committed
438 439 440 441 442 443 444
    if (project == 0) {
        qDebug() << "SessionManager::removeProject(0) ... THIS SHOULD NOT HAPPEN";
        return;
    }
    removeProjects(QList<Project*>() << project);
}

445 446
bool SessionManager::loadingSession()
{
hjk's avatar
hjk committed
447
    return d->m_loadingSession;
448 449
}

con's avatar
con committed
450 451 452
bool SessionManager::save()
{
    if (debug)
hjk's avatar
hjk committed
453
        qDebug() << "SessionManager - saving session" << d->m_sessionName;
con's avatar
con committed
454

hjk's avatar
hjk committed
455
    emit m_instance->aboutToSaveSession();
con's avatar
con committed
456

hjk's avatar
hjk committed
457 458 459
    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
460 461
                                                       QLatin1String("QtCreatorSession"));
    }
462

463
    QVariantMap data;
464
    // save the startup project
hjk's avatar
hjk committed
465
    if (d->m_startupProject)
466
        data.insert(QLatin1String("StartupProject"), d->m_startupProject->projectFilePath().toString());
467

hjk's avatar
hjk committed
468
    QColor c = StyleHelper::requestedBaseColor();
469 470 471 472 473
    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'));
474
        data.insert(QLatin1String("Color"), tmp);
475 476
    }

477
    QStringList projectFiles;
hjk's avatar
hjk committed
478
    foreach (Project *pro, d->m_projects)
479
        projectFiles << pro->projectFilePath().toString();
480 481

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

487
    data.insert(QLatin1String("ProjectList"), projectFiles);
488
    data.insert(QLatin1String("CascadeSetActive"), d->m_casadeSetActive);
489 490

    QMap<QString, QVariant> depMap;
hjk's avatar
hjk committed
491 492
    QMap<QString, QStringList>::const_iterator i = d->m_depMap.constBegin();
    while (i != d->m_depMap.constEnd()) {
493 494 495 496 497 498 499 500
        QString key = i.key();
        QStringList values;
        foreach (const QString &value, i.value()) {
            values << value;
        }
        depMap.insert(key, values);
        ++i;
    }
501
    data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
502
    data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
503

hjk's avatar
hjk committed
504
    QMap<QString, QVariant>::const_iterator it, end = d->m_values.constEnd();
505
    QStringList keys;
hjk's avatar
hjk committed
506
    for (it = d->m_values.constBegin(); it != end; ++it) {
507
        data.insert(QLatin1String("value-") + it.key(), it.value());
508 509 510
        keys << it.key();
    }

511
    data.insert(QLatin1String("valueKeys"), keys);
512

513
    bool result = d->m_writer->save(data, ICore::mainWindow());
con's avatar
con committed
514
    if (!result) {
515
        QMessageBox::warning(ICore::dialogParent(), tr("Error while saving session"),
hjk's avatar
hjk committed
516
            tr("Could not save session to file %1").arg(d->m_writer->fileName().toUserOutput()));
con's avatar
con committed
517 518 519 520 521 522 523 524 525
    }

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

    return result;
}

/*!
526
  Closes all projects
con's avatar
con committed
527
  */
528
void SessionManager::closeAllProjects()
con's avatar
con committed
529
{
530 531
    setStartupProject(0);
    removeProjects(projects());
con's avatar
con committed
532 533
}

534
QList<Project *> SessionManager::projects()
hjk's avatar
hjk committed
535 536 537 538 539
{
    return d->m_projects;
}

bool SessionManager::hasProjects()
con's avatar
con committed
540
{
hjk's avatar
hjk committed
541
    return !d->m_projects.isEmpty();
con's avatar
con committed
542 543
}

hjk's avatar
hjk committed
544
QStringList SessionManagerPrivate::dependencies(const QString &proName) const
con's avatar
con committed
545 546
{
    QStringList result;
547
    dependencies(proName, result);
con's avatar
con committed
548 549 550
    return result;
}

hjk's avatar
hjk committed
551
void SessionManagerPrivate::dependencies(const QString &proName, QStringList &result) const
552 553 554 555 556 557 558 559 560 561
{
    QStringList depends = m_depMap.value(proName);

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

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

562 563 564 565 566
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.
567
            const QList<Project *> projects = SessionManager::projects();
568 569 570
            if (projects.size() == 1)
                return projects.first()->displayName();
            return QString();
571 572
        } else if (Project *project = SessionManager::projectForFile(
                       Utils::FileName::fromString(filePath))) {
573 574 575 576 577 578 579 580 581 582 583 584
            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
585
QStringList SessionManagerPrivate::dependenciesOrder() const
con's avatar
con committed
586 587 588 589 590
{
    QList<QPair<QString, QStringList> > unordered;
    QStringList ordered;

    // copy the map to a temporary list
hjk's avatar
hjk committed
591
    foreach (Project *pro, m_projects) {
592
        const QString proName = pro->projectFilePath().toString();
hjk's avatar
hjk committed
593
        unordered << QPair<QString, QStringList>(proName, m_depMap.value(proName));
con's avatar
con committed
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
    }

    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
618
QList<Project *> SessionManager::projectOrder(Project *project)
con's avatar
con committed
619 620 621 622
{
    QList<Project *> result;

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

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

    return result;
}

640
QList<Node *> SessionManager::nodesForFile(const Utils::FileName &fileName)
641
{
642
    FindNodesForFileVisitor findNodes(fileName);
643 644 645 646 647 648
    sessionNode()->accept(&findNodes);
    return findNodes.nodes();
}

// node for file returns a randomly selected node if there are multiple
// prefer to use nodesForFile and figure out which node you want
649
Node *SessionManager::nodeForFile(const Utils::FileName &fileName)
650 651 652 653 654 655 656 657 658 659
{
    Node *node = 0;
    foreach (Node *n, nodesForFile(fileName)) {
        // prefer file nodes
        if (!node || (node->nodeType() != FileNodeType && n->nodeType() == FileNodeType))
            node = n;
    }
    return node;
}

hjk's avatar
hjk committed
660
Project *SessionManager::projectForNode(Node *node)
con's avatar
con committed
661 662 663 664
{
    if (!node)
        return 0;

665
    FolderNode *rootProjectNode = node->asFolderNode();
con's avatar
con committed
666 667
    if (!rootProjectNode)
        rootProjectNode = node->parentFolderNode();
hjk's avatar
hjk committed
668 669

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

dt's avatar
dt committed
672
    Q_ASSERT(rootProjectNode);
con's avatar
con committed
673

674
    return Utils::findOrDefault(d->m_projects, Utils::equal(&Project::rootProjectNode, rootProjectNode));
con's avatar
con committed
675 676
}

677
Project *SessionManager::projectForFile(const Utils::FileName &fileName)
con's avatar
con committed
678 679 680 681
{
    if (debug)
        qDebug() << "SessionManager::projectForFile(" << fileName << ")";

682 683
    const QList<Project *> &projectList = projects();
    foreach (Project *p, projectList)
684
        if (d->projectContainsFile(p, fileName))
685
            return p;
hjk's avatar
hjk committed
686

687 688 689
    return 0;
}

690
bool SessionManagerPrivate::projectContainsFile(Project *p, const Utils::FileName &fileName) const
691 692 693 694
{
    if (!m_projectFileCache.contains(p))
        m_projectFileCache.insert(p, p->files(Project::AllFiles));

695
    return m_projectFileCache.value(p).contains(fileName.toString());
con's avatar
con committed
696 697
}

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

708 709 710
void SessionManager::configureEditors(Project *project)
{
    foreach (IDocument *document, DocumentModel::openedDocuments()) {
711
        if (d->projectContainsFile(project, document->filePath())) {
712 713 714 715 716 717 718 719 720
            foreach (IEditor *editor, DocumentModel::editorsForDocument(document)) {
                if (TextEditor::BaseTextEditor *textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
                        project->editorConfiguration()->configureEditor(textEditor);
                }
            }
        }
    }
}

con's avatar
con committed
721 722 723 724 725 726
void SessionManager::removeProjects(QList<Project *> remove)
{
    QMap<QString, QStringList> resMap;

    foreach (Project *pro, remove) {
        if (debug)
727
            qDebug() << "SessionManager - emitting aboutToRemoveProject(" << pro->displayName() << ")";
hjk's avatar
hjk committed
728
        emit m_instance->aboutToRemoveProject(pro);
con's avatar
con committed
729 730 731 732 733 734 735
    }


    // Refresh dependencies
    QSet<QString> projectFiles;
    foreach (Project *pro, projects()) {
        if (!remove.contains(pro))
736
            projectFiles.insert(pro->projectFilePath().toString());
con's avatar
con committed
737 738 739 740 741
    }

    QSet<QString>::const_iterator i = projectFiles.begin();
    while (i != projectFiles.end()) {
        QStringList dependencies;
hjk's avatar
hjk committed
742
        foreach (const QString &dependency, d->m_depMap.value(*i)) {
con's avatar
con committed
743 744 745 746 747 748 749 750
            if (projectFiles.contains(dependency))
                dependencies << dependency;
        }
        if (!dependencies.isEmpty())
            resMap.insert(*i, dependencies);
        ++i;
    }

hjk's avatar
hjk committed
751
    d->m_depMap = resMap;
con's avatar
con committed
752

753 754 755 756 757
    // TODO: Clear m_modelProjectHash

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

hjk's avatar
hjk committed
760
        if (pro == d->m_startupProject)
761 762
            setStartupProject(0);

763 764
        disconnect(pro, &Project::fileListChanged,
                   m_instance, &SessionManager::clearProjectFileCache);
hjk's avatar
hjk committed
765
        d->m_projectFileCache.remove(pro);
766 767 768

        if (debug)
            qDebug() << "SessionManager - emitting projectRemoved(" << pro->displayName() << ")";
hjk's avatar
hjk committed
769 770
        d->m_sessionNode->removeProjectNodes(QList<ProjectNode *>() << pro->rootProjectNode());
        emit m_instance->projectRemoved(pro);
771 772 773
        delete pro;
    }

con's avatar
con committed
774
    if (startupProject() == 0)
hjk's avatar
hjk committed
775 776
        if (!d->m_projects.isEmpty())
            setStartupProject(d->m_projects.first());
con's avatar
con committed
777 778
}

779
/*!
780
    Lets other plugins store persistent values within the session file.
781 782
*/

con's avatar
con committed
783 784
void SessionManager::setValue(const QString &name, const QVariant &value)
{
hjk's avatar
hjk committed
785
    if (d->m_values.value(name) == value)
786
        return;
hjk's avatar
hjk committed
787
    d->m_values.insert(name, value);
788
    markSessionFileDirty(false);
con's avatar
con committed
789 790 791 792
}

QVariant SessionManager::value(const QString &name)
{
793
    QMap<QString, QVariant>::const_iterator it = d->m_values.constFind(name);
hjk's avatar
hjk committed
794
    return (it == d->m_values.constEnd()) ? QVariant() : *it;
con's avatar
con committed
795 796
}

hjk's avatar
hjk committed
797
QString SessionManager::activeSession()
con's avatar
con committed
798
{
hjk's avatar
hjk committed
799
    return d->m_sessionName;
con's avatar
con committed
800 801
}

hjk's avatar
hjk committed
802
QStringList SessionManager::sessions()
con's avatar
con committed
803
{
hjk's avatar
hjk committed
804
    if (d->m_sessions.isEmpty()) {
Tobias Hunger's avatar
Tobias Hunger committed
805
        // We are not initialized yet, so do that now
806
        QDir sessionDir(ICore::userResourcePath());
807
        QList<QFileInfo> sessionFiles = sessionDir.entryInfoList(QStringList() << QLatin1String("*.qws"), QDir::NoFilter, QDir::Time);
hjk's avatar
hjk committed
808
        foreach (const QFileInfo &fileInfo, sessionFiles) {
809
            if (fileInfo.completeBaseName() != QLatin1String("default"))
hjk's avatar
hjk committed
810
                d->m_sessions << fileInfo.completeBaseName();
811
        }
hjk's avatar
hjk committed
812
        d->m_sessions.prepend(QLatin1String("default"));
813
    }
hjk's avatar
hjk committed
814
    return d->m_sessions;
con's avatar
con committed
815 816
}

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

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

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

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

844 845 846 847 848 849

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

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

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

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

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

hjk's avatar
hjk committed
894
void SessionManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader)
895 896 897 898 899
{
    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();
900 901 902 903 904 905 906
        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);
907 908 909 910 911
        }
        ++i;
    }
}

hjk's avatar
hjk committed
912
void SessionManagerPrivate::askUserAboutFailedProjects()
913 914 915 916 917 918
{
    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
919 920
                                            SessionManager::tr("Failed to restore project files"),
                                            SessionManager::tr("Could not restore the following project files:<br><b>%1</b>").
921
                                            arg(fileList));
hjk's avatar
hjk committed
922 923
        QPushButton * keepButton = new QPushButton(SessionManager::tr("Keep projects in Session"), box);
        QPushButton * removeButton = new QPushButton(SessionManager::tr("Remove projects from Session"), box);
924 925 926 927 928 929 930 931 932 933
        box->addButton(keepButton, QMessageBox::AcceptRole);
        box->addButton(removeButton, QMessageBox::DestructiveRole);

        box->exec();

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

hjk's avatar
hjk committed
934
void SessionManagerPrivate::restoreStartupProject(const PersistentSettingsReader &reader)
935 936 937
{
    const QString startupProject = reader.restoreValue(QLatin1String("StartupProject")).toString();
    if (!startupProject.isEmpty()) {
hjk's avatar
hjk committed
938
        foreach (Project *pro, d->m_projects) {
939
            if (pro->projectFilePath().toString() == startupProject) {
hjk's avatar
hjk committed
940
                m_instance->setStartupProject(pro);
941 942 943
                break;
            }
        }
944 945
    }
    if (!m_startupProject) {
946 947
        if (!startupProject.isEmpty())
            qWarning() << "Could not find startup project" << startupProject;
hjk's avatar
hjk committed
948 949
        if (!m_projects.isEmpty())
            m_instance->setStartupProject(m_projects.first());