projectwizardpage.cpp 18 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
12
** 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
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** 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
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
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

con's avatar
con committed
31
32
33
#include "projectwizardpage.h"
#include "ui_projectwizardpage.h"

34
35
36
37
#include "addnewmodel.h"
#include "projectexplorer.h"
#include "session.h"

hjk's avatar
hjk committed
38
#include <coreplugin/icore.h>
39
#include <coreplugin/iversioncontrol.h>
40
#include <coreplugin/iwizardfactory.h>
41
42
#include <coreplugin/vcsmanager.h>
#include <extensionsystem/pluginmanager.h>
43
#include <utils/algorithm.h>
44
#include <utils/fileutils.h>
45
46
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
47
#include <utils/wizard.h>
48
49
#include <vcsbase/vcsbaseconstants.h>

50
51
#include <QDir>
#include <QTextStream>
52
#include <QTreeView>
con's avatar
con committed
53

54
55
56
/*!
    \class ProjectExplorer::Internal::ProjectWizardPage

57
58
    \brief The ProjectWizardPage class provides a wizard page showing projects
    and version control to add new files to.
59
60
61
62

    \sa ProjectExplorer::Internal::ProjectFileWizardExtension
*/

63
64
65
66
using namespace Core;

namespace ProjectExplorer {
namespace Internal {
con's avatar
con committed
67

68
69
70
71
72
73
74
75
// --------------------------------------------------------------------
// BestNodeSelector:
// --------------------------------------------------------------------

class BestNodeSelector
{
public:
    BestNodeSelector(const QString &commonDirectory, const QStringList &files);
76
    void inspect(AddNewTree *tree, bool isContextNode);
77
    AddNewTree *bestChoice() const;
78
    bool deploys();
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
    QString deployingProjects() const;
private:
    QString m_commonDirectory;
    QStringList m_files;
    bool m_deploys;
    QString m_deployText;
    AddNewTree *m_bestChoice;
    int m_bestMatchLength;
    int m_bestMatchPriority;
};

BestNodeSelector::BestNodeSelector(const QString &commonDirectory, const QStringList &files)
    : m_commonDirectory(commonDirectory),
      m_files(files),
      m_deploys(false),
      m_deployText(QCoreApplication::translate("ProjectWizard", "The files are implicitly added to the projects:") + QLatin1Char('\n')),
      m_bestChoice(0),
      m_bestMatchLength(-1),
      m_bestMatchPriority(-1)
{ }

// Find the project the new files should be added
// If any node deploys the files, then we don't want to add the files.
// Otherwise consider their common path. Either a direct match on the directory
// or the directory with the longest matching path (list containing"/project/subproject1"
// matching common path "/project/subproject1/newuserpath").
105
void BestNodeSelector::inspect(AddNewTree *tree, bool isContextNode)
106
107
108
109
110
111
112
113
114
115
116
117
{
    FolderNode *node = tree->node();
    if (node->nodeType() == ProjectNodeType) {
        if (static_cast<ProjectNode *>(node)->deploysFolder(m_commonDirectory)) {
            m_deploys = true;
            m_deployText += tree->displayName() + QLatin1Char('\n');
        }
    }
    if (m_deploys)
        return;
    const QString projectDirectory = ProjectExplorerPlugin::directoryFor(node);
    const int projectDirectorySize = projectDirectory.size();
118
    if (!m_commonDirectory.startsWith(projectDirectory) && !isContextNode)
119
        return;
120
121
122
123
    bool betterMatch = tree->priority() > 0
            && (projectDirectorySize > m_bestMatchLength
                || (projectDirectorySize == m_bestMatchLength && tree->priority() > m_bestMatchPriority));

124
125
126
127
128
129
130
131
132
133
134
135
136
137
    if (betterMatch) {
        m_bestMatchPriority = tree->priority();
        m_bestMatchLength = projectDirectorySize;
        m_bestChoice = tree;
    }
}

AddNewTree *BestNodeSelector::bestChoice() const
{
    if (m_deploys)
        return 0;
    return m_bestChoice;
}

138
139
140
141
142
bool BestNodeSelector::deploys()
{
    return m_deploys;
}

143
144
145
146
147
148
149
150
151
152
153
154
155
QString BestNodeSelector::deployingProjects() const
{
    if (m_deploys)
        return m_deployText;
    return QString();
}

// --------------------------------------------------------------------
// Helper:
// --------------------------------------------------------------------

static inline AddNewTree *createNoneNode(BestNodeSelector *selector)
{
156
157
158
    QString displayName = QCoreApplication::translate("ProjectWizard", "<None>");
    if (selector->deploys())
        displayName = QCoreApplication::translate("ProjectWizard", "<Implicitly Add>");
159
160
161
162
163
164
165
166
167
168
169
170
    return new AddNewTree(displayName);
}

static inline AddNewTree *buildAddProjectTree(ProjectNode *root, const QString &projectPath, Node *contextNode, BestNodeSelector *selector)
{
    QList<AddNewTree *> children;
    foreach (ProjectNode *pn, root->subProjectNodes()) {
        AddNewTree *child = buildAddProjectTree(pn, projectPath, contextNode, selector);
        if (child)
            children.append(child);
    }

171
172
    const QList<ProjectAction> &list = root->supportedActions(root);
    if (list.contains(AddSubProject) && !list.contains(InheritedFromParent)) {
173
174
175
        if (projectPath.isEmpty() || root->canAddSubProject(projectPath)) {
            FolderNode::AddNewInformation info = root->addNewInformation(QStringList() << projectPath, contextNode);
            AddNewTree *item = new AddNewTree(root, children, info);
176
            selector->inspect(item, root == contextNode);
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
            return item;
        }
    }

    if (children.isEmpty())
        return 0;
    return new AddNewTree(root, children, root->displayName());
}

static inline AddNewTree *buildAddProjectTree(SessionNode *root, const QString &projectPath, Node *contextNode, BestNodeSelector *selector)
{
    QList<AddNewTree *> children;
    foreach (ProjectNode *pn, root->projectNodes()) {
        AddNewTree *child = buildAddProjectTree(pn, projectPath, contextNode, selector);
        if (child)
            children.append(child);
    }
    children.prepend(createNoneNode(selector));
    return new AddNewTree(root, children, root->displayName());
}

static inline AddNewTree *buildAddFilesTree(FolderNode *root, const QStringList &files, Node *contextNode, BestNodeSelector *selector)
{
    QList<AddNewTree *> children;
    foreach (FolderNode *fn, root->subFolderNodes()) {
        AddNewTree *child = buildAddFilesTree(fn, files, contextNode, selector);
        if (child)
            children.append(child);
    }

207
208
    const QList<ProjectAction> &list = root->supportedActions(root);
    if (list.contains(AddNewFile) && !list.contains(InheritedFromParent)) {
209
210
        FolderNode::AddNewInformation info = root->addNewInformation(files, contextNode);
        AddNewTree *item = new AddNewTree(root, children, info);
211
        selector->inspect(item, root == contextNode);
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
        return item;
    }
    if (children.isEmpty())
        return 0;
    return new AddNewTree(root, children, root->displayName());
}

static inline AddNewTree *buildAddFilesTree(SessionNode *root, const QStringList &files, Node *contextNode, BestNodeSelector *selector)
{
    QList<AddNewTree *> children;
    foreach (ProjectNode *pn, root->projectNodes()) {
        AddNewTree *child = buildAddFilesTree(pn, files, contextNode, selector);
        if (child)
            children.append(child);
    }
    children.prepend(createNoneNode(selector));
    return new AddNewTree(root, children, root->displayName());
}

static inline AddNewTree *getChoices(const QStringList &generatedFiles,
                                     IWizardFactory::WizardKind wizardKind,
                                     Node *contextNode,
                                     BestNodeSelector *selector)
{
    if (wizardKind == IWizardFactory::ProjectWizard)
        return buildAddProjectTree(SessionManager::sessionNode(), generatedFiles.first(), contextNode, selector);
    else
        return buildAddFilesTree(SessionManager::sessionNode(), generatedFiles, contextNode, selector);
}

// --------------------------------------------------------------------
// ProjectWizardPage:
// --------------------------------------------------------------------

con's avatar
con committed
246
ProjectWizardPage::ProjectWizardPage(QWidget *parent) :
247
    WizardPage(parent),
248
    m_ui(new Ui::WizardPage),
249
250
    m_model(0),
    m_repositoryExists(false)
con's avatar
con committed
251
252
{
    m_ui->setupUi(this);
253
    m_ui->vcsManageButton->setText(Core::ICore::msgShowOptionsDialog());
254
255
    connect(m_ui->projectComboBox, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotProjectChanged(int)));
256
    connect(m_ui->vcsManageButton, SIGNAL(clicked()), this, SLOT(slotManageVcs()));
257
    setProperty(Utils::SHORT_TITLE_PROPERTY, tr("Summary"));
258
259
260

    connect(Core::VcsManager::instance(), SIGNAL(configurationChanged(const IVersionControl*)),
            this, SLOT(initializeVersionControls()));
con's avatar
con committed
261
262
263
264
265
}

ProjectWizardPage::~ProjectWizardPage()
{
    delete m_ui;
266
    delete m_model;
con's avatar
con committed
267
268
}

269
void ProjectWizardPage::setModel(AddNewModel *model)
con's avatar
con committed
270
{
271
272
273
274
275
276
277
278
279
    delete m_model;
    m_model = model;

    // TODO see OverViewCombo and OverView for click event filter
    m_ui->projectComboBox->setModel(model);
    bool enabled = m_model->rowCount(QModelIndex()) > 1;
    m_ui->projectComboBox->setEnabled(enabled);

    expandTree(QModelIndex());
con's avatar
con committed
280
281
}

282
bool ProjectWizardPage::expandTree(const QModelIndex &root)
283
{
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
    bool expand = false;
    if (!root.isValid()) // always expand root
        expand = true;

    // Check children
    int count = m_model->rowCount(root);
    for (int i = 0; i < count; ++i) {
        if (expandTree(m_model->index(i, 0, root)))
            expand = true;
    }

    // Apply to self
    if (expand)
        m_ui->projectComboBox->view()->expand(root);
    else
        m_ui->projectComboBox->view()->collapse(root);

    // if we are a high priority node, our *parent* needs to be expanded
    AddNewTree *tree = static_cast<AddNewTree *>(root.internalPointer());
    if (tree && tree->priority() >= 100)
        expand = true;

    return expand;
307
308
}

309
void ProjectWizardPage::setBestNode(AddNewTree *tree)
310
{
311
312
313
314
315
316
317
    QModelIndex index = m_model->indexForTree(tree);
    m_ui->projectComboBox->setCurrentIndex(index);

    while (index.isValid()) {
        m_ui->projectComboBox->view()->expand(index);
        index = index.parent();
    }
318
319
}

320
FolderNode *ProjectWizardPage::currentNode() const
con's avatar
con committed
321
{
322
323
    QModelIndex index = m_ui->projectComboBox->view()->currentIndex();
    return m_model->nodeForIndex(index);
con's avatar
con committed
324
325
}

326
void ProjectWizardPage::setAddingSubProject(bool addingSubProject)
con's avatar
con committed
327
{
328
329
330
    m_ui->projectLabel->setText(addingSubProject ?
                                    tr("Add as a subproject to project:")
                                  : tr("Add to &project:"));
con's avatar
con committed
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
void ProjectWizardPage::initializeVersionControls()
{
    // Figure out version control situation:
    // 1) Directory is managed and VCS supports "Add" -> List it
    // 2) Directory is managed and VCS does not support "Add" -> None available
    // 3) Directory is not managed -> Offer all VCS that support "CreateRepository"

    IVersionControl *currentSelection = 0;
    int currentIdx = versionControlIndex() - 1;
    if (currentIdx >= 0 && currentIdx <= m_activeVersionControls.size() - 1)
        currentSelection = m_activeVersionControls.at(currentIdx);

    m_activeVersionControls.clear();

    QStringList versionControlChoices = QStringList(tr("<None>"));
    if (!m_commonDirectory.isEmpty()) {
        IVersionControl *managingControl = VcsManager::findVersionControlForDirectory(m_commonDirectory);
        if (managingControl) {
            // Under VCS
            if (managingControl->supportsOperation(IVersionControl::AddOperation)) {
                versionControlChoices.append(managingControl->displayName());
                m_activeVersionControls.push_back(managingControl);
                m_repositoryExists = true;
            }
        } else {
            // Create
            foreach (IVersionControl *vc, ExtensionSystem::PluginManager::getObjects<IVersionControl>()) {
                if (vc->supportsOperation(IVersionControl::CreateRepositoryOperation)) {
                    versionControlChoices.append(vc->displayName());
                    m_activeVersionControls.append(vc);
                }
            }
            m_repositoryExists = false;
        }
    } // has a common root.

    setVersionControls(versionControlChoices);
    // Enable adding to version control by default.
    if (m_repositoryExists && versionControlChoices.size() >= 2)
        setVersionControlIndex(1);
    if (!m_repositoryExists) {
        int newIdx = m_activeVersionControls.indexOf(currentSelection) + 1;
        setVersionControlIndex(newIdx);
    }
}

bool ProjectWizardPage::runVersionControl(const QList<GeneratedFile> &files, QString *errorMessage)
{
    // Add files to  version control (Entry at 0 is 'None').
    const int vcsIndex = versionControlIndex() - 1;
    if (vcsIndex < 0 || vcsIndex >= m_activeVersionControls.size())
        return true;
    QTC_ASSERT(!m_commonDirectory.isEmpty(), return false);

    IVersionControl *versionControl = m_activeVersionControls.at(vcsIndex);
    // Create repository?
    if (!m_repositoryExists) {
        QTC_ASSERT(versionControl->supportsOperation(IVersionControl::CreateRepositoryOperation), return false);
        if (!versionControl->vcsCreateRepository(m_commonDirectory)) {
            *errorMessage = tr("A version control system repository could not be created in \"%1\".").arg(m_commonDirectory);
            return false;
        }
    }
    // Add files if supported.
    if (versionControl->supportsOperation(IVersionControl::AddOperation)) {
        foreach (const GeneratedFile &generatedFile, files) {
            if (!versionControl->vcsAdd(generatedFile.path())) {
                *errorMessage = tr("Failed to add \"%1\" to the version control system.").arg(generatedFile.path());
                return false;
            }
        }
    }
    return true;
}

408
409
410
411
412
413
414
415
416
417
418
419
void ProjectWizardPage::initializeProjectTree(Node *context, const QStringList &paths,
                                              IWizardFactory::WizardKind kind,
                                              ProjectAction action)
{
    BestNodeSelector selector(m_commonDirectory, paths);
    AddNewTree *tree = getChoices(paths, kind, context, &selector);

    setAdditionalInfo(selector.deployingProjects());

    AddNewModel *model = new AddNewModel(tree);
    setModel(model);
    setBestNode(selector.bestChoice());
420
    setAddingSubProject(action == AddSubProject);
421
422
}

dt's avatar
dt committed
423
424
425
426
427
428
429
430
431
432
433
void ProjectWizardPage::setNoneLabel(const QString &label)
{
    m_ui->projectComboBox->setItemText(0, label);
}

void ProjectWizardPage::setAdditionalInfo(const QString &text)
{
    m_ui->additionalInfo->setText(text);
    m_ui->additionalInfo->setVisible(!text.isEmpty());
}

434
void ProjectWizardPage::setVersionControls(const QStringList &vcs)
con's avatar
con committed
435
{
436
437
    m_ui->addToVersionControlComboBox->clear();
    m_ui->addToVersionControlComboBox->addItems(vcs);
con's avatar
con committed
438
439
}

440
int ProjectWizardPage::versionControlIndex() const
con's avatar
con committed
441
{
442
    return m_ui->addToVersionControlComboBox->currentIndex();
con's avatar
con committed
443
444
}

445
void ProjectWizardPage::setVersionControlIndex(int idx)
con's avatar
con committed
446
{
447
    m_ui->addToVersionControlComboBox->setCurrentIndex(idx);
con's avatar
con committed
448
449
}

450
void ProjectWizardPage::setFiles(const QStringList &fileNames)
con's avatar
con committed
451
{
452
    m_commonDirectory = Utils::commonPath(fileNames);
hjk's avatar
hjk committed
453
454
    QString fileMessage;
    {
con's avatar
con committed
455
        QTextStream str(&fileMessage);
456
        str << "<qt>"
457
            << (m_commonDirectory.isEmpty() ? tr("Files to be added:") : tr("Files to be added in"))
458
            << "<pre>";
459
460

        QStringList formattedFiles;
461
462
        if (m_commonDirectory.isEmpty()) {
            formattedFiles = fileNames;
463
        } else {
464
465
466
467
            str << QDir::toNativeSeparators(m_commonDirectory) << ":\n\n";
            const int prefixSize = m_commonDirectory.size() + 1;
            formattedFiles = Utils::transform(fileNames, [prefixSize](const QString &f)
                                                         { return f.mid(prefixSize); });
468
        }
469
470
471
472
473
474
475
476
477
478
        // Alphabetically, and files in sub-directories first
        Utils::sort(formattedFiles, [](const QString &filePath1, const QString &filePath2) -> bool {
            const bool filePath1HasDir = filePath1.contains(QLatin1Char('/'));
            const bool filePath2HasDir = filePath2.contains(QLatin1Char('/'));

            if (filePath1HasDir == filePath2HasDir)
                return Utils::FileName::fromString(filePath1) < Utils::FileName::fromString(filePath2);
            return filePath1HasDir;
        }
);
479
480
481
482

        foreach (const QString &f, formattedFiles)
            str << QDir::toNativeSeparators(f) << '\n';

con's avatar
con committed
483
484
485
486
        str << "</pre>";
    }
    m_ui->filesLabel->setText(fileMessage);
}
487
488
489
490
491
492
493
494
495
496
497

void ProjectWizardPage::setProjectToolTip(const QString &tt)
{
    m_ui->projectComboBox->setToolTip(tt);
    m_ui->projectLabel->setToolTip(tt);
}

void ProjectWizardPage::slotProjectChanged(int index)
{
    setProjectToolTip(index >= 0 && index < m_projectToolTips.size() ?
                      m_projectToolTips.at(index) : QString());
498
    emit projectNodeChanged();
499
}
500
501
502

void ProjectWizardPage::slotManageVcs()
{
hjk's avatar
hjk committed
503
    Core::ICore::showOptionsDialog(VcsBase::Constants::VCS_SETTINGS_CATEGORY,
504
505
                                   VcsBase::Constants::VCS_COMMON_SETTINGS_ID,
                                   this);
506
}
507
508
509

} // namespace Internal
} // namespace ProjectExplorer