qbsnodes.cpp 24.8 KB
Newer Older
Tobias Hunger's avatar
Tobias Hunger committed
1
2
/****************************************************************************
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
Tobias Hunger's avatar
Tobias Hunger committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** 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.
**
** GNU Lesser General Public License Usage
** 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
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "qbsnodes.h"

#include "qbsproject.h"
33
#include "qbsprojectmanagerconstants.h"
34
#include "qbsrunconfiguration.h"
Tobias Hunger's avatar
Tobias Hunger committed
35

36
#include <coreplugin/fileiconprovider.h>
37
#include <coreplugin/idocument.h>
38
#include <projectexplorer/projectexplorerconstants.h>
39
#include <projectexplorer/target.h>
40
#include <qtsupport/qtsupportconstants.h>
41
#include <utils/hostosinfo.h>
Tobias Hunger's avatar
Tobias Hunger committed
42
43
44
#include <utils/qtcassert.h>

#include <QDir>
45
#include <QStyle>
Tobias Hunger's avatar
Tobias Hunger committed
46
47
48
49
50

// ----------------------------------------------------------------------
// Helpers:
// ----------------------------------------------------------------------

51
52
53
54
55
56
57
static QString displayNameFromPath(const QString &path, const QString &base)
{
    QString dir = base;
    if (!base.endsWith(QLatin1Char('/')))
        dir.append(QLatin1Char('/'));

    QString name = path;
58
    if (name.startsWith(dir)) {
59
        name = name.mid(dir.count());
60
61
62
63
64
    } else {
        QFileInfo fi = QFileInfo(path);
        name = QCoreApplication::translate("Qbs::QbsProjectNode", "%1 in %2")
                .arg(fi.fileName(), fi.absolutePath());
    }
65
66
67

    return name;
}
Tobias Hunger's avatar
Tobias Hunger committed
68

69
static QIcon generateIcon(const QString &overlay)
70
71
{
    const QSize desiredSize = QSize(16, 16);
72
73
74
    const QIcon overlayIcon(overlay);
    const QPixmap pixmap
            = Core::FileIconProvider::overlayIcon(QStyle::SP_DirIcon, overlayIcon, desiredSize);
75
76

    QIcon result;
77
    result.addPixmap(pixmap);
78
79
80
81

    return result;
}

82
83
84
namespace QbsProjectManager {
namespace Internal {

85
86
87
QIcon QbsGroupNode::m_groupIcon;
QIcon QbsProjectNode::m_projectIcon;
QIcon QbsProductNode::m_productIcon;
88

89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
static QbsProjectNode *parentQbsProjectNode(ProjectExplorer::Node *node)
{
    for (ProjectExplorer::FolderNode *pn = node->projectNode(); pn; pn = pn->parentFolderNode()) {
        QbsProjectNode *prjNode = qobject_cast<QbsProjectNode *>(pn);
        if (prjNode)
            return prjNode;
    }
    return 0;
}

static QbsProductNode *parentQbsProductNode(ProjectExplorer::Node *node)
{
    for (; node; node = node->parentFolderNode()) {
        QbsProductNode *prdNode = qobject_cast<QbsProductNode *>(node);
        if (prdNode)
            return prdNode;
    }
    return 0;
}

109
110
111
112
113
114
115
116
117
static qbs::GroupData findMainQbsGroup(const qbs::ProductData &productData)
{
    foreach (const qbs::GroupData &grp, productData.groups()) {
        if (grp.name() == productData.name() && grp.location() == productData.location())
            return grp;
    }
    return qbs::GroupData();
}

Tobias Hunger's avatar
Tobias Hunger committed
118
119
class FileTreeNode {
public:
Tobias Hunger's avatar
Tobias Hunger committed
120
121
    explicit FileTreeNode(const QString &n = QString(), FileTreeNode *p = 0, bool f = false) :
        parent(p), name(n), m_isFile(f)
Tobias Hunger's avatar
Tobias Hunger committed
122
123
124
125
126
127
128
129
130
131
    {
        if (p)
            p->children.append(this);
    }

    ~FileTreeNode()
    {
        qDeleteAll(children);
    }

Tobias Hunger's avatar
Tobias Hunger committed
132
    FileTreeNode *addPart(const QString &n, bool isFile)
Tobias Hunger's avatar
Tobias Hunger committed
133
134
135
136
137
    {
        foreach (FileTreeNode *c, children) {
            if (c->name == n)
                return c;
        }
Tobias Hunger's avatar
Tobias Hunger committed
138
        return new FileTreeNode(n, this, isFile);
Tobias Hunger's avatar
Tobias Hunger committed
139
140
    }

Tobias Hunger's avatar
Tobias Hunger committed
141
    bool isFile() { return m_isFile; }
Tobias Hunger's avatar
Tobias Hunger committed
142

143
    static FileTreeNode *moveChildrenUp(FileTreeNode *node)
144
    {
145
        QTC_ASSERT(node, return 0);
146
147

        FileTreeNode *newParent = node->parent;
148
149
        if (!newParent)
            return 0;
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

        // disconnect node and parent:
        node->parent = 0;
        newParent->children.removeOne(node);

        foreach (FileTreeNode *c, node->children) {
            // update path, make sure there will be no / before "C:" on windows:
            if (Utils::HostOsInfo::isWindowsHost() && node->name.isEmpty())
                c->name = node->name;
            else
                c->name = node->name + QLatin1Char('/') + c->name;

            newParent->children.append(c);
            c->parent = newParent;
        }

        // Delete node
        node->children.clear();
        delete node;
169
170
171
172
173
174
175
        return newParent;
    }

    // Moves the children of the node pointing to basedir to the root of the tree.
    static void reorder(FileTreeNode *node, const QString &basedir)
    {
        QTC_CHECK(!basedir.isEmpty());
Tobias Hunger's avatar
Tobias Hunger committed
176
177
178
179
        QString prefix = basedir;
        if (basedir.startsWith(QLatin1Char('/')))
            prefix = basedir.mid(1);
        prefix.append(QLatin1Char('/'));
180
181
182
183
184
185
186
187
188

        if (node->path() == basedir) {
            // Find root node:
            FileTreeNode *root = node;
            while (root->parent)
                root = root->parent;

            foreach (FileTreeNode *c, node->children) {
                // Update children names by prepending basedir
Tobias Hunger's avatar
Tobias Hunger committed
189
                c->name = prefix + c->name;
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
                // Update parent information:
                c->parent = root;

                root->children.append(c);
            }

            // Clean up node:
            node->children.clear();
            node->parent->children.removeOne(node);
            node->parent = 0;
            delete node;

            return;
        }

        foreach (FileTreeNode *n, node->children)
            reorder(n, basedir);
207
208
    }

Tobias Hunger's avatar
Tobias Hunger committed
209
210
211
212
213
    static void simplify(FileTreeNode *node)
    {
        foreach (FileTreeNode *c, node->children)
            simplify(c);

214
215
216
        if (!node->parent)
            return;

Tobias Hunger's avatar
Tobias Hunger committed
217
        if (node->children.isEmpty() && !node->isFile()) {
218
219
220
221
222
223
224
            // Clean up empty folder nodes:
            node->parent->children.removeOne(node);
            node->parent = 0;
            delete node;
        } else if (node->children.count() == 1 && !node->children.at(0)->isFile()) {
            // Compact folder nodes with one child only:
            moveChildrenUp(node);
Tobias Hunger's avatar
Tobias Hunger committed
225
226
227
228
229
230
231
232
        }
    }

    QString path()
    {
        QString p = name;
        FileTreeNode *node = parent;
        while (node) {
233
234
            if (!Utils::HostOsInfo::isWindowsHost() || !node->name.isEmpty())
                p = node->name + QLatin1Char('/') + p;
Tobias Hunger's avatar
Tobias Hunger committed
235
236
237
238
239
240
241
242
            node = node->parent;
        }
        return p;
    }

    QList<FileTreeNode *> children;
    FileTreeNode *parent;
    QString name;
Tobias Hunger's avatar
Tobias Hunger committed
243
    bool m_isFile;
Tobias Hunger's avatar
Tobias Hunger committed
244
245
246
247
248
249
250
251
};

// ----------------------------------------------------------------------
// QbsFileNode:
// ----------------------------------------------------------------------

QbsFileNode::QbsFileNode(const QString &filePath, const ProjectExplorer::FileType fileType,
                         bool generated, int line) :
252
    ProjectExplorer::FileNode(filePath, fileType, generated, line)
Tobias Hunger's avatar
Tobias Hunger committed
253
254
{ }

255
256
QString QbsFileNode::displayName() const
{
257
258
259
260
    int l = line();
    if (l < 0)
        return ProjectExplorer::FileNode::displayName();
    return ProjectExplorer::FileNode::displayName() + QLatin1Char(':') + QString::number(l);
261
262
}

Tobias Hunger's avatar
Tobias Hunger committed
263
264
265
266
267
268
269
270
// ---------------------------------------------------------------------------
// QbsBaseProjectNode:
// ---------------------------------------------------------------------------

QbsBaseProjectNode::QbsBaseProjectNode(const QString &path) :
    ProjectExplorer::ProjectNode(path)
{ }

271
bool QbsBaseProjectNode::showInSimpleTree() const
Tobias Hunger's avatar
Tobias Hunger committed
272
273
274
275
{
    return false;
}

276
QList<ProjectExplorer::ProjectAction> QbsBaseProjectNode::supportedActions(ProjectExplorer::Node *node) const
Tobias Hunger's avatar
Tobias Hunger committed
277
278
{
    Q_UNUSED(node);
279
    return QList<ProjectExplorer::ProjectAction>();
Tobias Hunger's avatar
Tobias Hunger committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
}

bool QbsBaseProjectNode::canAddSubProject(const QString &proFilePath) const
{
    Q_UNUSED(proFilePath);
    return false;
}

bool QbsBaseProjectNode::addSubProjects(const QStringList &proFilePaths)
{
    Q_UNUSED(proFilePaths);
    return false;
}

bool QbsBaseProjectNode::removeSubProjects(const QStringList &proFilePaths)
{
    Q_UNUSED(proFilePaths);
    return false;
}

300
bool QbsBaseProjectNode::addFiles(const QStringList &filePaths, QStringList *notAdded)
Tobias Hunger's avatar
Tobias Hunger committed
301
302
303
304
305
306
{
    Q_UNUSED(filePaths);
    Q_UNUSED(notAdded);
    return false;
}

307
bool QbsBaseProjectNode::removeFiles(const QStringList &filePaths, QStringList *notRemoved)
Tobias Hunger's avatar
Tobias Hunger committed
308
309
310
311
312
313
{
    Q_UNUSED(filePaths);
    Q_UNUSED(notRemoved);
    return false;
}

314
bool QbsBaseProjectNode::deleteFiles(const QStringList &filePaths)
Tobias Hunger's avatar
Tobias Hunger committed
315
316
317
318
319
{
    Q_UNUSED(filePaths);
    return false;
}

320
bool QbsBaseProjectNode::renameFile(const QString &filePath, const QString &newFilePath)
Tobias Hunger's avatar
Tobias Hunger committed
321
322
323
324
325
326
327
328
329
330
{
    Q_UNUSED(filePath);
    Q_UNUSED(newFilePath);
    return false;
}

// --------------------------------------------------------------------
// QbsGroupNode:
// --------------------------------------------------------------------

331
QbsGroupNode::QbsGroupNode(const qbs::GroupData *grp, const QString &productPath) :
Tobias Hunger's avatar
Tobias Hunger committed
332
    QbsBaseProjectNode(QString()),
333
    m_qbsGroupData(0)
Tobias Hunger's avatar
Tobias Hunger committed
334
{
335
336
337
    if (m_groupIcon.isNull())
        m_groupIcon = QIcon(QString::fromLatin1(Constants::QBS_GROUP_ICON));

338
    setIcon(m_groupIcon);
339

340
    QbsFileNode *idx = new QbsFileNode(grp->location().fileName(),
341
                                       ProjectExplorer::ProjectFileType, false,
342
                                       grp->location().line());
343
    addFileNodes(QList<ProjectExplorer::FileNode *>() << idx);
344

345
    updateQbsGroupData(grp, productPath, true, true);
Tobias Hunger's avatar
Tobias Hunger committed
346
347
348
349
}

bool QbsGroupNode::isEnabled() const
{
350
351
352
353
    if (!parentFolderNode() || !m_qbsGroupData)
        return false;
    return static_cast<QbsProductNode *>(parentFolderNode())->isEnabled()
            && m_qbsGroupData->isEnabled();
Tobias Hunger's avatar
Tobias Hunger committed
354
355
}

356
357
358
QList<ProjectExplorer::ProjectAction> QbsGroupNode::supportedActions(ProjectExplorer::Node *node) const
{
    Q_UNUSED(node);
359
360
    return QList<ProjectExplorer::ProjectAction>() << ProjectExplorer::AddNewFile << ProjectExplorer::AddExistingFile
                                                   << ProjectExplorer::RemoveFile;
361
362
363
364
}

bool QbsGroupNode::addFiles(const QStringList &filePaths, QStringList *notAdded)
{
365
366
367
368
    QStringList notAddedDummy;
    if (!notAdded)
        notAdded = &notAddedDummy;

369
370
    QbsProjectNode *prjNode = parentQbsProjectNode(this);
    if (!prjNode || !prjNode->qbsProject().isValid()) {
371
        *notAdded += filePaths;
372
373
374
375
376
        return false;
    }

    QbsProductNode *prdNode = parentQbsProductNode(this);
    if (!prdNode || !prdNode->qbsProductData().isValid()) {
377
        *notAdded += filePaths;
378
379
380
        return false;
    }

381
382
    return prjNode->project()->addFilesToProduct(this, filePaths, prdNode->qbsProductData(),
                                                 *m_qbsGroupData, notAdded);
383
384
}

385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
bool QbsGroupNode::removeFiles(const QStringList &filePaths, QStringList *notRemoved)
{
    QStringList notRemovedDummy;
    if (!notRemoved)
        notRemoved = &notRemovedDummy;

    QbsProjectNode *prjNode = parentQbsProjectNode(this);
    if (!prjNode || !prjNode->qbsProject().isValid()) {
        *notRemoved += filePaths;
        return false;
    }

    QbsProductNode *prdNode = parentQbsProductNode(this);
    if (!prdNode || !prdNode->qbsProductData().isValid()) {
        *notRemoved += filePaths;
        return false;
    }

403
404
    return prjNode->project()->removeFilesFromProduct(this, filePaths, prdNode->qbsProductData(),
                                                      *m_qbsGroupData, notRemoved);
405
406
}

407
408
void QbsGroupNode::updateQbsGroupData(const qbs::GroupData *grp, const QString &productPath,
                                      bool productWasEnabled, bool productIsEnabled)
Tobias Hunger's avatar
Tobias Hunger committed
409
{
410
411
    Q_ASSERT(grp);

412
    if (grp == m_qbsGroupData && productPath == m_productPath)
Tobias Hunger's avatar
Tobias Hunger committed
413
414
        return;

415
416
417
418
    bool groupWasEnabled = productWasEnabled && m_qbsGroupData && m_qbsGroupData->isEnabled();
    bool groupIsEnabled = productIsEnabled && grp->isEnabled();
    bool updateExisting = groupWasEnabled != groupIsEnabled;

419
    m_productPath = productPath;
420
    m_qbsGroupData = grp;
421

422
    setPath(grp->location().fileName());
423
424
425
426
427
428
429
    setDisplayName(grp->name());

    QbsFileNode *idx = 0;
    foreach (ProjectExplorer::FileNode *fn, fileNodes()) {
        idx = qobject_cast<QbsFileNode *>(fn);
        if (idx)
            break;
Tobias Hunger's avatar
Tobias Hunger committed
430
    }
Tobias Hunger's avatar
Tobias Hunger committed
431
432
    QTC_ASSERT(idx, return);
    idx->setPathAndLine(grp->location().fileName(), grp->location().line());
Tobias Hunger's avatar
Tobias Hunger committed
433

434
    setupFiles(this, grp->allFilePaths(), productPath, updateExisting);
435

436
437
    if (updateExisting)
        emitNodeUpdated();
438
439
}

440
void QbsGroupNode::setupFiles(QbsBaseProjectNode *root, const QStringList &files,
441
                              const QString &productPath, bool updateExisting)
442
{
Tobias Hunger's avatar
Tobias Hunger committed
443
    // Build up a tree of nodes:
444
    FileTreeNode tree;
Tobias Hunger's avatar
Tobias Hunger committed
445

446
    foreach (const QString &path, files) {
Tobias Hunger's avatar
Tobias Hunger committed
447
448
        QStringList pathSegments = path.split(QLatin1Char('/'), QString::SkipEmptyParts);

449
        FileTreeNode *root = &tree;
Tobias Hunger's avatar
Tobias Hunger committed
450
451
452
453
        while (!pathSegments.isEmpty()) {
            bool isFile = pathSegments.count() == 1;
            root = root->addPart(pathSegments.takeFirst(), isFile);
        }
Tobias Hunger's avatar
Tobias Hunger committed
454
455
    }

456
    FileTreeNode::reorder(&tree, productPath);
457
458
    FileTreeNode::simplify(&tree);

459
    setupFolder(root, &tree, productPath, updateExisting);
Tobias Hunger's avatar
Tobias Hunger committed
460
461
}

462
void QbsGroupNode::setupFolder(ProjectExplorer::FolderNode *root,
463
464
                               const FileTreeNode *fileTree, const QString &baseDir,
                               bool updateExisting)
Tobias Hunger's avatar
Tobias Hunger committed
465
{
466
467
468
469
470
    // We only need to care about FileNodes and FolderNodes here. Everything else is
    // handled elsewhere.
    // QbsGroupNodes are managed by the QbsProductNode.
    // The buildsystem file is either managed by QbsProductNode or by updateQbsGroupData(...).

471
472
    QList<ProjectExplorer::FileNode *> filesToRemove;
    foreach (ProjectExplorer::FileNode *fn, root->fileNodes()) {
473
474
        if (!qobject_cast<QbsFileNode *>(fn))
            filesToRemove << fn;
Tobias Hunger's avatar
Tobias Hunger committed
475
476
477
    }
    QList<ProjectExplorer::FileNode *> filesToAdd;

478
479
    QList<ProjectExplorer::FolderNode *> foldersToRemove;
    foreach (ProjectExplorer::FolderNode *fn, root->subFolderNodes()) {
480
481
482
        if (fn->nodeType() == ProjectExplorer::ProjectNodeType)
            continue; // Skip ProjectNodes mixed into the folders...
        foldersToRemove.append(fn);
483
    }
Tobias Hunger's avatar
Tobias Hunger committed
484

485
    foreach (FileTreeNode *c, fileTree->children) {
Tobias Hunger's avatar
Tobias Hunger committed
486
        QString path = c->path();
487
488

        // Handle files:
Tobias Hunger's avatar
Tobias Hunger committed
489
        if (c->isFile()) {
490
491
492
493
494
495
496
497
            ProjectExplorer::FileNode *fn = 0;
            foreach (ProjectExplorer::FileNode *f, root->fileNodes()) {
                // There can be one match only here!
                if (f->path() != path)
                    continue;
                fn = f;
                break;
            }
Tobias Hunger's avatar
Tobias Hunger committed
498
499
            if (fn) {
                filesToRemove.removeOne(fn);
500
501
                if (updateExisting)
                    fn->emitNodeUpdated();
Tobias Hunger's avatar
Tobias Hunger committed
502
503
504
505
            } else {
                fn = new ProjectExplorer::FileNode(path, ProjectExplorer::UnknownFileType, false);
                filesToAdd.append(fn);
            }
506
            continue;
Tobias Hunger's avatar
Tobias Hunger committed
507
        } else {
508
509
510
511
512
513
514
515
            ProjectExplorer::FolderNode *fn = 0;
            foreach (ProjectExplorer::FolderNode *f, root->subFolderNodes()) {
                // There can be one match only here!
                if (f->path() != path)
                    continue;
                fn = f;
                break;
            }
516
517
            if (!fn) {
                fn = new FolderNode(c->path());
518
                root->addFolderNodes(QList<FolderNode *>() << fn);
519
520
            } else {
                foldersToRemove.removeOne(fn);
521
522
                if (updateExisting)
                    fn->emitNodeUpdated();
523
524
525
            }
            fn->setDisplayName(displayNameFromPath(c->path(), baseDir));

526
            setupFolder(fn, c, c->path(), updateExisting);
Tobias Hunger's avatar
Tobias Hunger committed
527
528
        }
    }
529
530
531
    root->removeFileNodes(filesToRemove);
    root->removeFolderNodes(foldersToRemove);
    root->addFileNodes(filesToAdd);
Tobias Hunger's avatar
Tobias Hunger committed
532
533
534
535
536
537
}

// --------------------------------------------------------------------
// QbsProductNode:
// --------------------------------------------------------------------

538
539
QbsProductNode::QbsProductNode(const qbs::ProductData &prd) :
    QbsBaseProjectNode(prd.location().fileName())
Tobias Hunger's avatar
Tobias Hunger committed
540
{
541
542
543
    if (m_productIcon.isNull())
        m_productIcon = generateIcon(QString::fromLatin1(Constants::QBS_PRODUCT_OVERLAY_ICON));

544
    setIcon(m_productIcon);
545

546
    ProjectExplorer::FileNode *idx = new QbsFileNode(prd.location().fileName(),
547
                                                     ProjectExplorer::ProjectFileType, false,
548
                                                     prd.location().line());
549
    addFileNodes(QList<ProjectExplorer::FileNode *>() << idx);
550

551
    setQbsProductData(prd);
Tobias Hunger's avatar
Tobias Hunger committed
552
553
554
555
}

bool QbsProductNode::isEnabled() const
{
556
    return m_qbsProductData.isEnabled();
Tobias Hunger's avatar
Tobias Hunger committed
557
558
}

559
bool QbsProductNode::showInSimpleTree() const
560
561
562
563
{
    return true;
}

564
565
566
QList<ProjectExplorer::ProjectAction> QbsProductNode::supportedActions(ProjectExplorer::Node *node) const
{
    Q_UNUSED(node);
567
568
    return QList<ProjectExplorer::ProjectAction>() << ProjectExplorer::AddNewFile << ProjectExplorer::AddExistingFile
                                                   << ProjectExplorer::RemoveFile;
569
570
571
572
}

bool QbsProductNode::addFiles(const QStringList &filePaths, QStringList *notAdded)
{
573
574
575
576
    QStringList notAddedDummy;
    if (!notAdded)
        notAdded = &notAddedDummy;

577
578
    QbsProjectNode *prjNode = parentQbsProjectNode(this);
    if (!prjNode || !prjNode->qbsProject().isValid()) {
579
        *notAdded += filePaths;
580
581
582
        return false;
    }

583
584
    qbs::GroupData grp = findMainQbsGroup(m_qbsProductData);
    if (grp.isValid()) {
585
586
        return prjNode->project()->addFilesToProduct(this, filePaths, m_qbsProductData, grp,
                                                     notAdded);
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
    }

    QTC_ASSERT(false, return false);
}

bool QbsProductNode::removeFiles(const QStringList &filePaths, QStringList *notRemoved)
{
    QStringList notRemovedDummy;
    if (!notRemoved)
        notRemoved = &notRemovedDummy;

    QbsProjectNode *prjNode = parentQbsProjectNode(this);
    if (!prjNode || !prjNode->qbsProject().isValid()) {
        *notRemoved += filePaths;
        return false;
    }

    qbs::GroupData grp = findMainQbsGroup(m_qbsProductData);
    if (grp.isValid()) {
606
607
        return prjNode->project()->removeFilesFromProduct(this, filePaths, m_qbsProductData, grp,
                                                          notRemoved);
608
609
610
611
612
    }

    QTC_ASSERT(false, return false);
}

613
void QbsProductNode::setQbsProductData(const qbs::ProductData prd)
Tobias Hunger's avatar
Tobias Hunger committed
614
{
615
    if (m_qbsProductData == prd)
Tobias Hunger's avatar
Tobias Hunger committed
616
617
        return;

Christian Kandeler's avatar
Christian Kandeler committed
618
    bool productWasEnabled = m_qbsProductData.isValid() && m_qbsProductData.isEnabled();
619
    bool productIsEnabled = prd.isEnabled();
620
621
    bool updateExisting = productWasEnabled != productIsEnabled;

622
623
624
    setDisplayName(prd.name());
    setPath(prd.location().fileName());
    const QString &productPath = QFileInfo(prd.location().fileName()).absolutePath();
Tobias Hunger's avatar
Tobias Hunger committed
625

626
    // Find the QbsFileNode we added earlier:
627
    QbsFileNode *idx = 0;
628
629
630
631
    foreach (ProjectExplorer::FileNode *fn, fileNodes()) {
        idx = qobject_cast<QbsFileNode *>(fn);
        if (idx)
            break;
Tobias Hunger's avatar
Tobias Hunger committed
632
    }
Tobias Hunger's avatar
Tobias Hunger committed
633
634
    QTC_ASSERT(idx, return);
    idx->setPathAndLine(prd.location().fileName(), prd.location().line());
Tobias Hunger's avatar
Tobias Hunger committed
635
636
637
638

    QList<ProjectExplorer::ProjectNode *> toAdd;
    QList<ProjectExplorer::ProjectNode *> toRemove = subProjectNodes();

639
640
    foreach (const qbs::GroupData &grp, prd.groups()) {
        if (grp.name() == prd.name() && grp.location() == prd.location()) {
641
            // Set implicit product group right onto this node:
642
            QbsGroupNode::setupFiles(this, grp.allFilePaths(), productPath, updateExisting);
643
644
            continue;
        }
645
646
647
        QbsGroupNode *gn = findGroupNode(grp.name());
        if (gn) {
            toRemove.removeOne(gn);
648
            gn->updateQbsGroupData(&grp, productPath, productWasEnabled, productIsEnabled);
Tobias Hunger's avatar
Tobias Hunger committed
649
        } else {
650
651
            gn = new QbsGroupNode(&grp, productPath);
            toAdd.append(gn);
Tobias Hunger's avatar
Tobias Hunger committed
652
653
654
655
656
657
        }
    }

    addProjectNodes(toAdd);
    removeProjectNodes(toRemove);

658
    m_qbsProductData = prd;
659
660
    if (updateExisting)
        emitNodeUpdated();
Tobias Hunger's avatar
Tobias Hunger committed
661
662
}

663
QList<ProjectExplorer::RunConfiguration *> QbsProductNode::runConfigurations() const
664
665
666
{
    QList<ProjectExplorer::RunConfiguration *> result;
    QbsProjectNode *pn = qobject_cast<QbsProjectNode *>(projectNode());
667
668
    if (!isEnabled() || !pn || !pn->qbsProject().isValid()
            || pn->qbsProject().targetExecutable(m_qbsProductData, qbs::InstallOptions()).isEmpty()) {
669
        return result;
670
    }
671
672
673
674
675

    foreach (ProjectExplorer::RunConfiguration *rc, pn->project()->activeTarget()->runConfigurations()) {
        QbsRunConfiguration *qbsRc = qobject_cast<QbsRunConfiguration *>(rc);
        if (!qbsRc)
            continue;
676
        if (qbsRc->qbsProduct() == qbsProductData().name())
677
678
679
680
681
682
            result << qbsRc;
    }

    return result;
}

Tobias Hunger's avatar
Tobias Hunger committed
683
684
685
686
QbsGroupNode *QbsProductNode::findGroupNode(const QString &name)
{
    foreach (ProjectExplorer::ProjectNode *n, subProjectNodes()) {
        QbsGroupNode *qn = static_cast<QbsGroupNode *>(n);
687
        if (qn->qbsGroupData()->name() == name)
Tobias Hunger's avatar
Tobias Hunger committed
688
689
690
691
692
693
694
695
696
            return qn;
    }
    return 0;
}

// --------------------------------------------------------------------
// QbsProjectNode:
// --------------------------------------------------------------------

Tobias Hunger's avatar
Tobias Hunger committed
697
QbsProjectNode::QbsProjectNode(const QString &path) :
698
    QbsBaseProjectNode(path)
Tobias Hunger's avatar
Tobias Hunger committed
699
700
{
    ctor();
Tobias Hunger's avatar
Tobias Hunger committed
701
702
703
704
}

QbsProjectNode::~QbsProjectNode()
{
705
    // do not delete m_project
Tobias Hunger's avatar
Tobias Hunger committed
706
707
}

708
709
710
711
712
713
bool QbsProjectNode::addFiles(const QStringList &filePaths, QStringList *notAdded)
{
    QbsProductNode *prd = findProductNode(displayName());
    return prd ? prd->addFiles(filePaths, notAdded) : false;
}

Tobias Hunger's avatar
Tobias Hunger committed
714
void QbsProjectNode::update(const qbs::ProjectData &prjData)
Tobias Hunger's avatar
Tobias Hunger committed
715
716
717
718
{
    QList<ProjectExplorer::ProjectNode *> toAdd;
    QList<ProjectExplorer::ProjectNode *> toRemove = subProjectNodes();

Tobias Hunger's avatar
Tobias Hunger committed
719
720
721
    foreach (const qbs::ProjectData &subData, prjData.subProjects()) {
        QbsProjectNode *qn = findProjectNode(subData.name());
        if (!qn) {
722
            QbsProjectNode *subProject = new QbsProjectNode(subData.location().fileName());
Tobias Hunger's avatar
Tobias Hunger committed
723
724
725
726
727
728
729
            subProject->update(subData);
            toAdd << subProject;
        } else {
            qn->update(subData);
            toRemove.removeOne(qn);
        }
    }
Tobias Hunger's avatar
Tobias Hunger committed
730

Tobias Hunger's avatar
Tobias Hunger committed
731
732
733
    foreach (const qbs::ProductData &prd, prjData.products()) {
        QbsProductNode *qn = findProductNode(prd.name());
        if (!qn) {
734
            toAdd << new QbsProductNode(prd);
Tobias Hunger's avatar
Tobias Hunger committed
735
        } else {
736
            qn->setQbsProductData(prd);
Tobias Hunger's avatar
Tobias Hunger committed
737
            toRemove.removeOne(qn);
Tobias Hunger's avatar
Tobias Hunger committed
738
739
740
        }
    }

741
742
743
    if (!prjData.name().isEmpty())
        setDisplayName(prjData.name());
    else
744
        setDisplayName(project()->displayName());
Tobias Hunger's avatar
Tobias Hunger committed
745
746
747
748
749

    removeProjectNodes(toRemove);
    addProjectNodes(toAdd);
}

750
751
QbsProject *QbsProjectNode::project() const
{
752
    return static_cast<QbsProjectNode *>(parentFolderNode())->project();
753
754
}

755
const qbs::Project QbsProjectNode::qbsProject() const
Tobias Hunger's avatar
Tobias Hunger committed
756
{
757
    return project()->qbsProject();
Tobias Hunger's avatar
Tobias Hunger committed
758
759
}

760
const qbs::ProjectData QbsProjectNode::qbsProjectData() const
Tobias Hunger's avatar
Tobias Hunger committed
761
{
762
    return project()->qbsProjectData();
Tobias Hunger's avatar
Tobias Hunger committed
763
764
}

765
766
767
768
769
bool QbsProjectNode::showInSimpleTree() const
{
    return true;
}

Tobias Hunger's avatar
Tobias Hunger committed
770
771
void QbsProjectNode::ctor()
{
772
773
774
    if (m_projectIcon.isNull())
        m_projectIcon = generateIcon(QString::fromLatin1(QtSupport::Constants::ICON_QT_PROJECT));

Tobias Hunger's avatar
Tobias Hunger committed
775
776
    setIcon(m_projectIcon);
    addFileNodes(QList<ProjectExplorer::FileNode *>()
777
                 << new ProjectExplorer::FileNode(path(), ProjectExplorer::ProjectFileType, false));
Tobias Hunger's avatar
Tobias Hunger committed
778
779
}

Tobias Hunger's avatar
Tobias Hunger committed
780
781
782
QbsProductNode *QbsProjectNode::findProductNode(const QString &name)
{
    foreach (ProjectExplorer::ProjectNode *n, subProjectNodes()) {
Tobias Hunger's avatar
Tobias Hunger committed
783
        QbsProductNode *qn = qobject_cast<QbsProductNode *>(n);
784
        if (qn && qn->qbsProductData().name() == name)
Tobias Hunger's avatar
Tobias Hunger committed
785
786
787
788
789
790
791
792
793
            return qn;
    }
    return 0;
}

QbsProjectNode *QbsProjectNode::findProjectNode(const QString &name)
{
    foreach (ProjectExplorer::ProjectNode *n, subProjectNodes()) {
        QbsProjectNode *qn = qobject_cast<QbsProjectNode *>(n);
794
        if (qn && qn->qbsProjectData().name() == name)
Tobias Hunger's avatar
Tobias Hunger committed
795
796
797
798
799
            return qn;
    }
    return 0;
}

800
801
802
803
804
805
806
807
808
809
810
811

QbsRootProjectNode::QbsRootProjectNode(QbsProject *project) :
    QbsProjectNode(project->projectFilePath().toString()),
    m_project(project)
{
}

void QbsRootProjectNode::update()
{
    update(m_project->qbsProjectData());
}

Tobias Hunger's avatar
Tobias Hunger committed
812
813
} // namespace Internal
} // namespace QbsProjectManager