qtoptionspage.cpp 33.9 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Tobias Hunger's avatar
Tobias Hunger committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
Tobias Hunger's avatar
Tobias Hunger committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Tobias Hunger's avatar
Tobias Hunger 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.
Tobias Hunger's avatar
Tobias Hunger committed
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
****************************************************************************/
Tobias Hunger's avatar
Tobias Hunger committed
30

31
#include "qtoptionspage.h"
32
#include "qtconfigwidget.h"
33 34
#include "ui_showbuildlog.h"
#include "ui_qtversionmanager.h"
35 36
#include "ui_qtversioninfo.h"
#include "ui_debugginghelper.h"
37
#include "qtsupportconstants.h"
38
#include "qtversionmanager.h"
dt's avatar
dt committed
39
#include "qtversionfactory.h"
40
#include "qmldumptool.h"
41 42

#include <coreplugin/progressmanager/progressmanager.h>
43
#include <coreplugin/coreconstants.h>
hjk's avatar
hjk committed
44
#include <coreplugin/variablechooser.h>
45
#include <projectexplorer/toolchain.h>
46
#include <projectexplorer/toolchainmanager.h>
47
#include <projectexplorer/projectexplorerconstants.h>
48
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
49 50
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
51
#include <utils/runextensions.h>
52
#include <utils/algorithm.h>
53

54 55 56
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
57 58
#include <QTextBrowser>
#include <QDesktopServices>
59

hjk's avatar
hjk committed
60
using namespace ProjectExplorer;
61
using namespace Utils;
62

hjk's avatar
hjk committed
63 64 65 66 67
namespace QtSupport {
namespace Internal {

enum ModelRoles { VersionIdRole = Qt::UserRole, ToolChainIdRole, BuildLogRole, BuildRunningRole};

68 69 70 71 72
///
// QtOptionsPage
///

QtOptionsPage::QtOptionsPage()
dt's avatar
dt committed
73
    : m_widget(0)
74
{
hjk's avatar
hjk committed
75
    setId(Constants::QTVERSION_SETTINGS_PAGE_ID);
76
    setDisplayName(QCoreApplication::translate("Qt4ProjectManager", Constants::QTVERSION_SETTINGS_PAGE_NAME));
hjk's avatar
hjk committed
77
    setCategory(ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_CATEGORY);
78 79 80
    setDisplayCategory(QCoreApplication::translate("ProjectExplorer",
        ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_TR_CATEGORY));
    setCategoryIcon(QLatin1String(ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_CATEGORY_ICON));
81 82
}

83
QWidget *QtOptionsPage::widget()
84
{
85 86
    if (!m_widget)
        m_widget = new QtOptionsPageWidget;
87 88 89 90 91
    return m_widget;
}

void QtOptionsPage::apply()
{
92 93
    if (!m_widget) // page was never shown
        return;
94
    m_widget->apply();
95 96
}

97
void QtOptionsPage::finish()
98
{
99
    delete m_widget;
100 101
}

102
//-----------------------------------------------------
103 104


105
QtOptionsPageWidget::QtOptionsPageWidget(QWidget *parent)
106 107
    : QWidget(parent)
    , m_specifyNameString(tr("<specify a name>"))
108
    , m_ui(new Internal::Ui::QtVersionManager())
109 110
    , m_versionUi(new Internal::Ui::QtVersionInfo())
    , m_debuggingHelperUi(new Internal::Ui::DebuggingHelper())
111
    , m_infoBrowser(new QTextBrowser)
112 113
    , m_invalidVersionIcon(QLatin1String(Core::Constants::ICON_ERROR))
    , m_warningVersionIcon(QLatin1String(Core::Constants::ICON_WARNING))
dt's avatar
dt committed
114
    , m_configurationWidget(0)
115 116
    , m_autoItem(0)
    , m_manualItem(0)
117
{
118 119
    QWidget *versionInfoWidget = new QWidget();
    m_versionUi->setupUi(versionInfoWidget);
120
    m_versionUi->editPathPushButton->setText(PathChooser::browseButtonLabel());
121

122 123 124
    QWidget *debuggingHelperDetailsWidget = new QWidget();
    m_debuggingHelperUi->setupUi(debuggingHelperDetailsWidget);

125
    m_ui->setupUi(this);
126

127 128 129 130
    m_infoBrowser->setOpenLinks(false);
    m_infoBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction);
    connect(m_infoBrowser, SIGNAL(anchorClicked(QUrl)), this, SLOT(infoAnchorClicked(QUrl)));
    m_ui->infoWidget->setWidget(m_infoBrowser);
131
    connect(m_ui->infoWidget, SIGNAL(expanded(bool)),
132
            this, SLOT(setInfoWidgetVisibility()));
133

134
    m_ui->versionInfoWidget->setWidget(versionInfoWidget);
hjk's avatar
hjk committed
135
    m_ui->versionInfoWidget->setState(DetailsWidget::NoSummary);
136 137

    m_ui->debuggingHelperWidget->setWidget(debuggingHelperDetailsWidget);
138
    connect(m_ui->debuggingHelperWidget, SIGNAL(expanded(bool)),
139
            this, SLOT(setInfoWidgetVisibility()));
140

141
    // setup parent items for auto-detected and manual versions
dt_'s avatar
dt_ committed
142
    m_ui->qtdirList->header()->setStretchLastSection(false);
143 144
    m_ui->qtdirList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
    m_ui->qtdirList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
dt_'s avatar
dt_ committed
145
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
146 147 148 149 150 151 152 153 154
    m_autoItem = new QTreeWidgetItem(m_ui->qtdirList);
    m_autoItem->setText(0, tr("Auto-detected"));
    m_autoItem->setFirstColumnSpanned(true);
    m_autoItem->setFlags(Qt::ItemIsEnabled);
    m_manualItem = new QTreeWidgetItem(m_ui->qtdirList);
    m_manualItem->setText(0, tr("Manual"));
    m_manualItem->setFirstColumnSpanned(true);
    m_manualItem->setFlags(Qt::ItemIsEnabled);

155
    QList<int> additions = transform(QtVersionManager::versions(), &BaseQtVersion::uniqueId);
156 157

    updateQtVersions(additions, QList<int>(), QList<int>());
158 159

    m_ui->qtdirList->expandAll();
160

Robert Loehning's avatar
Robert Loehning committed
161
    connect(m_versionUi->nameEdit, SIGNAL(textEdited(QString)),
162 163
            this, SLOT(updateCurrentQtName()));

dt_'s avatar
dt_ committed
164 165 166
    connect(m_versionUi->editPathPushButton, SIGNAL(clicked()),
            this, SLOT(editPath()));

167 168 169 170 171
    connect(m_ui->addButton, SIGNAL(clicked()),
            this, SLOT(addQtDir()));
    connect(m_ui->delButton, SIGNAL(clicked()),
            this, SLOT(removeQtDir()));

Robert Loehning's avatar
Robert Loehning committed
172 173
    connect(m_ui->qtdirList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
            this, SLOT(versionChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
174

175
    connect(m_debuggingHelperUi->rebuildButton, SIGNAL(clicked()),
176
            this, SLOT(buildDebuggingHelper()));
177 178 179 180
    connect(m_debuggingHelperUi->qmlDumpBuildButton, SIGNAL(clicked()),
            this, SLOT(buildQmlDump()));

    connect(m_debuggingHelperUi->showLogButton, SIGNAL(clicked()),
181
            this, SLOT(slotShowDebuggingBuildLog()));
182 183
    connect(m_debuggingHelperUi->toolChainComboBox, SIGNAL(activated(int)),
            this, SLOT(selectedToolChainChanged(int)));
184

185
    connect(m_ui->cleanUpButton, SIGNAL(clicked()), this, SLOT(cleanUpQtVersions()));
dt's avatar
dt committed
186 187
    userChangedCurrentVersion();
    updateCleanUpButton();
188

189 190
    connect(QtVersionManager::instance(), SIGNAL(dumpUpdatedFor(Utils::FileName)),
            this, SLOT(qtVersionsDumpUpdated(Utils::FileName)));
191

192 193 194
    connect(QtVersionManager::instance(), SIGNAL(qtVersionsChanged(QList<int>,QList<int>,QList<int>)),
            this, SLOT(updateQtVersions(QList<int>,QList<int>,QList<int>)));

195 196
    connect(ProjectExplorer::ToolChainManager::instance(), SIGNAL(toolChainsChanged()),
            this, SLOT(toolChainsUpdated()));
hjk's avatar
hjk committed
197 198

    auto chooser = new Core::VariableChooser(this);
199
    chooser->addSupportedWidget(m_versionUi->nameEdit, "Qt:Name");
hjk's avatar
hjk committed
200 201 202 203 204
    chooser->addMacroExpanderProvider(
        [this]() -> Utils::MacroExpander * {
            BaseQtVersion *version = currentVersion();
            return version ? version->macroExpander() : 0;
        });
205 206
}

207
int QtOptionsPageWidget::currentIndex() const
208
{
209 210 211 212
    if (QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        return indexForTreeItem(currentItem);
    return -1;
}
213

dt's avatar
dt committed
214
BaseQtVersion *QtOptionsPageWidget::currentVersion() const
215 216 217
{
    const int currentItemIndex = currentIndex();
    if (currentItemIndex >= 0 && currentItemIndex < m_versions.size())
218
        return m_versions.at(currentItemIndex);
219 220
    return 0;
}
221

dt's avatar
dt committed
222
static inline int findVersionById(const QList<BaseQtVersion *> &l, int id)
223 224 225
{
    const int size = l.size();
    for (int i = 0; i < size; i++)
226
        if (l.at(i)->uniqueId() == id)
227 228 229
            return i;
    return -1;
}
230

231
// Update with results of terminated helper build
232
void QtOptionsPageWidget::debuggingHelperBuildFinished(int qtVersionId, const QString &output, DebuggingHelperBuildTask::Tools tools)
233
{
234
    const int index = findVersionById(m_versions, qtVersionId);
235 236
    if (index == -1)
        return; // Oops, somebody managed to delete the version
237

dt's avatar
dt committed
238
    BaseQtVersion *version = m_versions.at(index);
239

240 241
    // Update item view
    QTreeWidgetItem *item = treeItemForIndex(index);
242 243 244 245 246
    QTC_ASSERT(item, return);
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags &= ~tools;
    item->setData(0, BuildRunningRole,  QVariant::fromValue(buildFlags));
247
    item->setData(0, BuildLogRole, output);
248

249 250
    bool success = true;
    if (tools & DebuggingHelperBuildTask::QmlDump)
dt's avatar
dt committed
251
        success &= version->hasQmlDump();
252

253 254
    if (!success)
        showDebuggingBuildLog(item);
255 256

    updateDebuggingHelperUi();
257 258
}

259 260 261
void QtOptionsPageWidget::cleanUpQtVersions()
{
    QStringList toRemove;
dt's avatar
dt committed
262
    foreach (const BaseQtVersion *v, m_versions) {
263
        if (!v->isValid())
264 265 266 267 268 269
            toRemove.append(v->displayName());
    }

    if (toRemove.isEmpty())
        return;

Friedemann Kleint's avatar
Friedemann Kleint committed
270
    if (QMessageBox::warning(0, tr("Remove Invalid Qt Versions"),
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
                             tr("Do you want to remove all invalid Qt Versions?<br>"
                                "<ul><li>%1</li></ul><br>"
                                "will be removed.").arg(toRemove.join(QLatin1String("</li><li>"))),
                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
        return;

    for (int i = m_versions.count() - 1; i >= 0; --i) {
        if (!m_versions.at(i)->isValid()) {
            QTreeWidgetItem *item = treeItemForIndex(i);
            delete item;

            delete m_versions.at(i);
            m_versions.removeAt(i);
        }
    }
dt's avatar
dt committed
286
    updateCleanUpButton();
287 288
}

289 290 291 292
void QtOptionsPageWidget::toolChainsUpdated()
{
    for (int i = 0; i < m_versions.count(); ++i) {
        QTreeWidgetItem *item = treeItemForIndex(i);
293
        if (item == m_ui->qtdirList->currentItem()) {
294
            updateDescriptionLabel();
295 296
            updateDebuggingHelperUi();
        } else {
297 298 299 300 301 302
            const ValidityInfo info = validInformation(m_versions.at(i));
            item->setIcon(0, info.icon);
        }
    }
}

303 304 305 306 307 308 309 310 311 312 313 314 315 316
void QtOptionsPageWidget::selectedToolChainChanged(int comboIndex)
{
    const int index = currentIndex();
    if (index < 0)
        return;

    QTreeWidgetItem *item = treeItemForIndex(index);
    QTC_ASSERT(item, return);

    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(comboIndex).toString();

    item->setData(0, ToolChainIdRole, toolChainId);
}

hjk's avatar
hjk committed
317
void QtOptionsPageWidget::qtVersionsDumpUpdated(const FileName &qmakeCommand)
318 319 320 321 322
{
    foreach (BaseQtVersion *version, m_versions) {
        if (version->qmakeCommand() == qmakeCommand)
            version->recheckDumper();
    }
323 324
    if (currentVersion()
            && currentVersion()->qmakeCommand() == qmakeCommand) {
325 326 327 328 329 330
        updateWidgets();
        updateDescriptionLabel();
        updateDebuggingHelperUi();
    }
}

331
void QtOptionsPageWidget::setInfoWidgetVisibility()
332
{
333 334 335 336
    m_ui->versionInfoWidget->setVisible((m_ui->infoWidget->state() == DetailsWidget::Collapsed)
                                        && (m_ui->debuggingHelperWidget->state() == DetailsWidget::Collapsed));
    m_ui->infoWidget->setVisible(m_ui->debuggingHelperWidget->state() == DetailsWidget::Collapsed);
    m_ui->debuggingHelperWidget->setVisible(m_ui->infoWidget->state() == DetailsWidget::Collapsed);
337 338
}

339 340 341 342 343
void QtOptionsPageWidget::infoAnchorClicked(const QUrl &url)
{
    QDesktopServices::openUrl(url);
}

344 345 346 347 348 349 350 351
QtOptionsPageWidget::ValidityInfo QtOptionsPageWidget::validInformation(const BaseQtVersion *version)
{
    ValidityInfo info;
    info.icon = m_validVersionIcon;

    if (!version)
        return info;

352
    info.description = tr("Qt version %1 for %2").arg(version->qtVersionString(), version->description());
353 354 355 356 357 358 359 360 361
    if (!version->isValid()) {
        info.icon = m_invalidVersionIcon;
        info.message = version->invalidReason();
        return info;
    }

    // Do we have tool chain issues?
    QStringList missingToolChains;
    int abiCount = 0;
hjk's avatar
hjk committed
362 363 364
    foreach (const Abi &abi, version->qtAbis()) {
        if (ToolChainManager::findToolChains(abi).isEmpty())
            missingToolChains.append(abi.toString());
365 366 367
        ++abiCount;
    }

368
    bool useable = true;
369
    QStringList warnings;
370 371 372
    if (!missingToolChains.isEmpty()) {
        if (missingToolChains.count() == abiCount) {
            // Yes, this Qt version can't be used at all!
373
            info.message = tr("No compiler can produce code for this Qt version. Please define one or more compilers.");
374 375 376 377
            info.icon = m_invalidVersionIcon;
            useable = false;
        } else {
            // Yes, some ABIs are unsupported
378
            warnings << tr("Not all possible target environments can be supported due to missing compilers.");
379 380 381 382
            info.toolTip = tr("The following ABIs are currently not supported:<ul><li>%1</li></ul>")
                    .arg(missingToolChains.join(QLatin1String("</li><li>")));
            info.icon = m_warningVersionIcon;
        }
383
    }
384 385

    if (useable) {
386 387
        warnings += version->warningReason();
        if (!warnings.isEmpty()) {
hjk's avatar
hjk committed
388
            info.message = warnings.join(QLatin1Char('\n'));
389 390 391 392
            info.icon = m_warningVersionIcon;
        }
    }

393 394 395
    return info;
}

hjk's avatar
hjk committed
396
QList<ToolChain*> QtOptionsPageWidget::toolChains(const BaseQtVersion *version)
397
{
398
    QList<ToolChain*> toolChains;
399
    if (!version)
400 401 402 403 404 405 406 407 408 409 410
        return toolChains;

    QSet<QString> ids;
    foreach (const Abi &a, version->qtAbis()) {
        foreach (ToolChain *tc, ToolChainManager::findToolChains(a)) {
            if (ids.contains(tc->id()))
                continue;
            ids.insert(tc->id());
            toolChains.append(tc);
        }
    }
411

412
    return toolChains;
413 414
}

415 416
QString QtOptionsPageWidget::defaultToolChainId(const BaseQtVersion *version)
{
hjk's avatar
hjk committed
417
    QList<ToolChain*> possibleToolChains = toolChains(version);
418 419 420 421 422
    if (!possibleToolChains.isEmpty())
        return possibleToolChains.first()->id();
    return QString();
}

423
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
424 425 426 427 428
{
    const int index = currentIndex();
    if (index < 0)
        return;

429 430 431
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

432 433
    QTreeWidgetItem *item = treeItemForIndex(index);
    QTC_ASSERT(item, return);
434

435 436 437 438
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags |= tools;
    item->setData(0, BuildRunningRole, QVariant::fromValue(buildFlags));
439

dt's avatar
dt committed
440
    BaseQtVersion *version = m_versions.at(index);
441 442 443
    if (!version)
        return;

444
    updateDebuggingHelperUi();
445

446
    // Run a debugging helper build task in the background.
447 448
    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(
                m_debuggingHelperUi->toolChainComboBox->currentIndex()).toString();
hjk's avatar
hjk committed
449
    ToolChain *toolChain = ToolChainManager::findToolChain(toolChainId);
450 451 452 453
    if (!toolChain)
        return;

    DebuggingHelperBuildTask *buildTask = new DebuggingHelperBuildTask(version, toolChain, tools);
454 455
    // Don't open General Messages pane with errors
    buildTask->showOutputOnError(false);
456 457
    connect(buildTask, SIGNAL(finished(int,QString,DebuggingHelperBuildTask::Tools)),
            this, SLOT(debuggingHelperBuildFinished(int,QString,DebuggingHelperBuildTask::Tools)),
458
            Qt::QueuedConnection);
459
    QFuture<void> task = QtConcurrent::run(&DebuggingHelperBuildTask::run, buildTask);
460
    const QString taskName = tr("Building Helpers");
461

462
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
463
}
464 465 466 467 468 469

void QtOptionsPageWidget::buildQmlDump()
{
    buildDebuggingHelper(DebuggingHelperBuildTask::QmlDump);
}

470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
// Non-modal dialog
class BuildLogDialog : public QDialog {
public:
    explicit BuildLogDialog(QWidget *parent = 0);
    void setText(const QString &text);

private:
    Ui_ShowBuildLog m_ui;
};

BuildLogDialog::BuildLogDialog(QWidget *parent) : QDialog(parent)
{
    m_ui.setupUi(this);
    setAttribute(Qt::WA_DeleteOnClose, true);
}

void BuildLogDialog::setText(const QString &text)
487
{
488 489 490 491
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
492

493 494 495 496 497 498 499 500 501
void QtOptionsPageWidget::slotShowDebuggingBuildLog()
{
    if (const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        showDebuggingBuildLog(currentItem);
}

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
502 503
    if (currentItemIndex < 0)
        return;
504
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
505
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
506
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
507
    dialog->show();
508 509
}

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
void QtOptionsPageWidget::updateQtVersions(const QList<int> &additions, const QList<int> &removals,
                                           const QList<int> &changes)
{
    QList<QTreeWidgetItem *> toRemove;
    QList<int> toAdd = additions;

    // Generate list of all existing items:
    QList<QTreeWidgetItem *> itemList;
    for (int i = 0; i < m_autoItem->childCount(); ++i)
        itemList.append(m_autoItem->child(i));
    for (int i = 0; i < m_manualItem->childCount(); ++i)
        itemList.append(m_manualItem->child(i));

    // Find existing items to remove/change:
    foreach (QTreeWidgetItem *item, itemList) {
        int id = item->data(0, VersionIdRole).toInt();
        if (removals.contains(id)) {
            toRemove.append(item);
            continue;
        }

        if (changes.contains(id)) {
            toAdd.append(id);
            toRemove.append(item);
            continue;
        }
    }

    // Remove changed/removed items:
    foreach (QTreeWidgetItem *item, toRemove) {
        int index = indexForTreeItem(item);
        delete m_versions.at(index);
        m_versions.removeAt(index);
        delete item;
    }

    // Add changed/added items:
    foreach (int a, toAdd) {
hjk's avatar
hjk committed
548
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574
        m_versions.append(version);
        QTreeWidgetItem *item = new QTreeWidgetItem;

        item->setText(0, version->displayName());
        item->setText(1, version->qmakeCommand().toUserOutput());
        item->setData(0, VersionIdRole, version->uniqueId());
        item->setData(0, ToolChainIdRole, defaultToolChainId(version));
        const ValidityInfo info = validInformation(version);
        item->setIcon(0, info.icon);

        // Insert in the right place:
        QTreeWidgetItem *parent = version->isAutodetected()? m_autoItem : m_manualItem;
        for (int i = 0; i < parent->childCount(); ++i) {
            BaseQtVersion *currentVersion = m_versions.at(indexForTreeItem(parent->child(i)));
            if (currentVersion->qtVersion() > version->qtVersion())
                continue;
            parent->insertChild(i, item);
            parent = 0;
            break;
        }

        if (parent)
            parent->addChild(item);
    }
}

575
QtOptionsPageWidget::~QtOptionsPageWidget()
576 577
{
    delete m_ui;
dt's avatar
dt committed
578 579
    delete m_versionUi;
    delete m_debuggingHelperUi;
dt's avatar
dt committed
580
    delete m_configurationWidget;
581
    qDeleteAll(m_versions);
582 583
}

dt_'s avatar
dt_ committed
584
static QString filterForQmakeFileDialog()
585
{
586
    QString filter = QLatin1String("qmake (");
hjk's avatar
hjk committed
587
    const QStringList commands = BuildableHelperLibrary::possibleQMakeCommands();
588 589 590
    for (int i = 0; i < commands.size(); ++i) {
        if (i)
            filter += QLatin1Char(' ');
hjk's avatar
hjk committed
591
        if (HostOsInfo::isMacHost())
592 593
            // work around QTBUG-7739 that prohibits filters that don't start with *
            filter += QLatin1Char('*');
594
        filter += commands.at(i);
hjk's avatar
hjk committed
595
        if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
596 597 598
            // kde bug, we need at least one wildcard character
            // see QTCREATORBUG-7771
            filter += QLatin1Char('*');
dt's avatar
dt committed
599
    }
600
    filter += QLatin1Char(')');
dt_'s avatar
dt_ committed
601 602
    return filter;
}
603

dt_'s avatar
dt_ committed
604 605
void QtOptionsPageWidget::addQtDir()
{
hjk's avatar
hjk committed
606
    FileName qtVersion = FileName::fromString(
Tobias Hunger's avatar
Tobias Hunger committed
607
                QFileDialog::getOpenFileName(this,
Friedemann Kleint's avatar
Friedemann Kleint committed
608
                                             tr("Select a qmake Executable"),
Tobias Hunger's avatar
Tobias Hunger committed
609 610 611 612
                                             QString(),
                                             filterForQmakeFileDialog(),
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt's avatar
dt committed
613 614
    if (qtVersion.isNull())
        return;
615 616 617 618

    QFileInfo fi(qtVersion.toString());
    // should add all qt versions here ?
    if (BuildableHelperLibrary::isQtChooser(fi))
hjk's avatar
hjk committed
619
        qtVersion = FileName::fromString(BuildableHelperLibrary::qtChooserToQmakePath(fi.symLinkTarget()));
620

621 622
    BaseQtVersion *version = Utils::findOrDefault(m_versions,
                                                  Utils::equal(&BaseQtVersion::qmakeCommand, qtVersion));
623
    if (version) {
dt's avatar
dt committed
624
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
625
        QMessageBox::warning(this, tr("Qt Version Already Known"),
626 627 628
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
629
    }
630

631 632
    QString error;
    version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error);
dt's avatar
dt committed
633 634
    if (version) {
        m_versions.append(version);
635

dt's avatar
dt committed
636 637
        QTreeWidgetItem *item = new QTreeWidgetItem(m_ui->qtdirList->topLevelItem(1));
        item->setText(0, version->displayName());
638
        item->setText(1, version->qmakeCommand().toUserOutput());
dt's avatar
dt committed
639
        item->setData(0, VersionIdRole, version->uniqueId());
640
        item->setData(0, ToolChainIdRole, defaultToolChainId(version));
dt's avatar
dt committed
641 642 643 644
        item->setIcon(0, version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
        m_ui->qtdirList->setCurrentItem(item); // should update the rest of the ui
        m_versionUi->nameEdit->setFocus();
        m_versionUi->nameEdit->selectAll();
645
    } else {
646
        QMessageBox::warning(this, tr("Qmake Not Executable"),
Friedemann Kleint's avatar
Friedemann Kleint committed
647
                             tr("The qmake executable %1 could not be added: %2").arg(qtVersion.toUserOutput()).arg(error));
648
        return;
dt's avatar
dt committed
649 650
    }
    updateCleanUpButton();
651 652
}

653
void QtOptionsPageWidget::removeQtDir()
654 655
{
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
656
    int index = indexForTreeItem(item);
657 658 659 660 661
    if (index < 0)
        return;

    delete item;

dt's avatar
dt committed
662
    BaseQtVersion *version = m_versions.at(index);
663
    m_versions.removeAt(index);
664
    delete version;
dt's avatar
dt committed
665
    updateCleanUpButton();
666 667
}

dt_'s avatar
dt_ committed
668 669
void QtOptionsPageWidget::editPath()
{
670
    BaseQtVersion *current = currentVersion();
671
    QString dir = currentVersion()->qmakeCommand().toFileInfo().absolutePath();
hjk's avatar
hjk committed
672
    FileName qtVersion = FileName::fromString(
673
                QFileDialog::getOpenFileName(this,
Robert Loehning's avatar
Robert Loehning committed
674
                                             tr("Select a qmake Executable"),
675 676 677 678
                                             dir,
                                             filterForQmakeFileDialog(),
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt_'s avatar
dt_ committed
679 680 681
    if (qtVersion.isNull())
        return;
    BaseQtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion);
Daniel Teske's avatar
Daniel Teske committed
682 683
    if (!version)
        return;
dt_'s avatar
dt_ committed
684 685 686
    // Same type? then replace!
    if (current->type() != version->type()) {
        // not the same type, error out
Friedemann Kleint's avatar
Friedemann Kleint committed
687
        QMessageBox::critical(this, tr("Incompatible Qt Versions"),
Friedemann Kleint's avatar
Friedemann Kleint committed
688
                              tr("The Qt version selected must match the device type."),
dt_'s avatar
dt_ committed
689 690 691 692 693 694
                              QMessageBox::Ok);
        delete version;
        return;
    }
    // same type, replace
    version->setId(current->uniqueId());
695 696
    if (current->unexpandedDisplayName() != current->defaultUnexpandedDisplayName(current->qmakeCommand()))
        version->setUnexpandedDisplayName(current->displayName());
dt_'s avatar
dt_ committed
697 698 699 700 701 702 703
    m_versions.replace(m_versions.indexOf(current), version);
    delete current;

    // Update ui
    userChangedCurrentVersion();
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
    item->setText(0, version->displayName());
704
    item->setText(1, version->qmakeCommand().toUserOutput());
dt_'s avatar
dt_ committed
705
    item->setData(0, VersionIdRole, version->uniqueId());
706
    item->setData(0, ToolChainIdRole, defaultToolChainId(version));
dt_'s avatar
dt_ committed
707 708 709
    item->setIcon(0, version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
}

710
void QtOptionsPageWidget::updateDebuggingHelperUi()
711
{
dt's avatar
dt committed
712
    BaseQtVersion *version = currentVersion();
713
    const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem();
714

hjk's avatar
hjk committed
715
    QList<ToolChain*> toolchains = toolChains(currentVersion());
716 717

    if (!version || !version->isValid() || toolchains.isEmpty()) {
718
        m_ui->debuggingHelperWidget->setVisible(false);
719
    } else {
720 721
        const DebuggingHelperBuildTask::Tools availableTools = DebuggingHelperBuildTask::availableTools(version);
        const bool canBuildQmlDumper = availableTools & DebuggingHelperBuildTask::QmlDump;
722

723
        const bool hasQmlDumper = version->hasQmlDump();
724
        const bool needsQmlDumper = version->needsQmlDump();
725

726 727 728 729 730 731 732 733
        bool isBuildingQmlDumper = false;

        if (currentItem) {
            DebuggingHelperBuildTask::Tools buildingTools
                    = currentItem->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
            isBuildingQmlDumper = buildingTools & DebuggingHelperBuildTask::QmlDump;
        }

734 735
        // get names of tools from labels
        QStringList helperNames;
736
        const QChar colon = QLatin1Char(':');
737
        if (hasQmlDumper)
738
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
739 740 741

        QString status;
        if (helperNames.isEmpty()) {
742
            status = tr("Helpers: None available");
743
        } else {
Friedemann Kleint's avatar
Friedemann Kleint committed
744
            //: %1 is list of tool names.
745
            status = tr("Helpers: %1.").arg(helperNames.join(QLatin1String(", ")));
746 747 748 749
        }

        m_ui->debuggingHelperWidget->setSummaryText(status);

750
        QString qmlDumpStatusText, qmlDumpStatusToolTip;
751
        Qt::TextInteractionFlags qmlDumpStatusTextFlags = Qt::NoTextInteraction;
752
        if (hasQmlDumper) {
753 754
            qmlDumpStatusText = QDir::toNativeSeparators(version->qmlDumpTool(false));
            const QString debugQmlDumpPath = QDir::toNativeSeparators(version->qmlDumpTool(true));
755
            if (qmlDumpStatusText != debugQmlDumpPath) {
756 757
                if (!qmlDumpStatusText.isEmpty()
                        && !debugQmlDumpPath.isEmpty())
758 759 760
                    qmlDumpStatusText += QLatin1String("\n");
                qmlDumpStatusText += debugQmlDumpPath;
            }
761
            qmlDumpStatusTextFlags = Qt::TextSelectableByMouse;
762
        } else {
763 764 765
            if (!needsQmlDumper) {
                qmlDumpStatusText = tr("<i>Not needed.</i>");
            } else if (canBuildQmlDumper) {
766 767 768
                qmlDumpStatusText = tr("<i>Not yet built.</i>");
            } else {
                qmlDumpStatusText = tr("<i>Cannot be compiled.</i>");
769
                QmlDumpTool::canBuild(version, &qmlDumpStatusToolTip);
770
            }
771
        }
772
        m_debuggingHelperUi->qmlDumpStatus->setText(qmlDumpStatusText);
773
        m_debuggingHelperUi->qmlDumpStatus->setTextInteractionFlags(qmlDumpStatusTextFlags);
774
        m_debuggingHelperUi->qmlDumpStatus->setToolTip(qmlDumpStatusToolTip);
775
        m_debuggingHelperUi->qmlDumpBuildButton->setEnabled(canBuildQmlDumper & !isBuildingQmlDumper);
776

hjk's avatar
hjk committed
777
        QList<ToolChain*> toolchains = toolChains(currentVersion());
778
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
779 780 781 782 783 784 785 786 787 788 789 790
        m_debuggingHelperUi->toolChainComboBox->clear();
        for (int i = 0; i < toolchains.size(); ++i) {
            if (!toolchains.at(i)->isValid())
                continue;
            if (i >= m_debuggingHelperUi->toolChainComboBox->count()) {
                m_debuggingHelperUi->toolChainComboBox->insertItem(i, toolchains.at(i)->displayName(),
                                                                   toolchains.at(i)->id());
            }
            if (toolchains.at(i)->id() == selectedToolChainId)
                m_debuggingHelperUi->toolChainComboBox->setCurrentIndex(i);
        }

791 792 793
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

794 795
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
796 797 798

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
799
        setInfoWidgetVisibility();
800 801 802
    }
}

Friedemann Kleint's avatar
Friedemann Kleint committed
803
// To be called if a Qt version was removed or added
dt's avatar
dt committed
804
void QtOptionsPageWidget::updateCleanUpButton()
805
{
806
    bool hasInvalidVersion = false;
807 808
    for (int i = 0; i < m_versions.count(); ++i) {
        if (!m_versions.at(i)->isValid()) {
809
            hasInvalidVersion = true;
dt's avatar
dt committed
810
            break;
811 812
        }
    }
813
    m_ui->cleanUpButton->setEnabled(hasInvalidVersion);
814
}
815

dt's avatar
dt committed
816
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
817
{
dt's avatar
dt committed
818 819 820
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
821 822
}

dt's avatar
dt committed
823
void QtOptionsPageWidget::qtVersionChanged()
824
{
825
    updateDescriptionLabel();
dt's avatar
dt committed
826
    updateDebuggingHelperUi();
827 828 829 830
}

void QtOptionsPageWidget::updateDescriptionLabel()
{
831
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
832 833 834 835 836 837 838 839 840 841
    const BaseQtVersion *version = currentVersion();
    const ValidityInfo info = validInformation(version);
    if (info.message.isEmpty()) {
        m_versionUi->errorLabel->setVisible(false);
    } else {
        m_versionUi->errorLabel->setVisible(true);
        m_versionUi->errorLabel->setText(info.message);
        m_versionUi->errorLabel->setToolTip(info.toolTip);
    }
    m_ui->infoWidget->setSummaryText(info.description);
842 843
    if (item)
        item->setIcon(0, info.icon);
844 845 846

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
847
        setInfoWidgetVisibility();
848
    } else {
849
        m_infoBrowser->clear();
850
        m_ui->versionInfoWidget->setVisible(false);
851
        m_ui->infoWidget->setVisible(false);
852
        m_ui->debuggingHelperWidget->setVisible(false);
853
    }
854 855
}

856
int QtOptionsPageWidget::indexForTreeItem(const QTreeWidgetItem *item) const
857 858 859
{
    if (!item || !item->parent())
        return -1;
860
    const int uniqueId = item->data(0, VersionIdRole).toInt();
861 862 863 864 865 866 867
    for (int index = 0; index < m_versions.size(); ++index) {
        if (m_versions.at(index)->uniqueId() == uniqueId)
            return index;
    }
    return -1;
}

868 869
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
870
    const int uniqueId = m_versions.at(index)->uniqueId();