qtoptionspage.cpp 35 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
#include <utils/treemodel.h>
54

55 56 57
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
58 59
#include <QTextBrowser>
#include <QDesktopServices>
60 61 62
#include <QSortFilterProxyModel>

#include <utility>
63

hjk's avatar
hjk committed
64
using namespace ProjectExplorer;
65
using namespace Utils;
66

hjk's avatar
hjk committed
67 68 69
namespace QtSupport {
namespace Internal {

70 71 72 73 74 75 76 77 78 79 80 81 82 83
class QtVersionItem : public TreeItem
{
public:
    QtVersionItem(const QString &name) // for auto/manual node
        : TreeItem({name})
    {}

    QtVersionItem(BaseQtVersion *version) // for versions
        : TreeItem(),
          m_version(version)
    {}

    ~QtVersionItem()
    {
Daniel Teske's avatar
Daniel Teske committed
84
        delete m_version;
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
    }

    void setVersion(BaseQtVersion *version)
    {
        m_version = version;
        update();
    }

    int uniqueId() const
    {
        return m_version ? m_version->uniqueId() : -1;
    }

    BaseQtVersion *version() const
    {
        return m_version;
    }

    QVariant data(int column, int role) const
    {
        if (!m_version)
            return TreeItem::data(column, role);

        if (role == Qt::DisplayRole) {
            if (column == 0)
                return m_version->displayName();
            if (column == 1)
                return m_version->qmakeCommand().toUserOutput();
        }

        if (role == Qt::DecorationRole && column == 0)
            return m_icon;

        return QVariant();
    }

    void setIcon(const QIcon &icon)
    {
        m_icon = icon;
        update();
    }

    QString buildLog() const
    {
        return m_buildLog;
    }

    void setBuildLog(const QString &buildLog)
    {
        m_buildLog = buildLog;
    }

    QByteArray toolChainId() const
    {
        return m_toolChainId;
    }

    void setToolChainId(const QByteArray &id)
    {
        m_toolChainId = id;
    }

    DebuggingHelperBuildTask::Tools buildFlags() const
    {
        return m_buildFlags;
    }

    void setBuildFlags(DebuggingHelperBuildTask::Tools flags)
    {
        m_buildFlags = flags;
    }

private:
Daniel Teske's avatar
Daniel Teske committed
158
    BaseQtVersion *m_version = 0;
159 160 161 162 163
    QIcon m_icon;
    QString m_buildLog;
    QByteArray m_toolChainId;
    DebuggingHelperBuildTask::Tools m_buildFlags;
};
hjk's avatar
hjk committed
164

165 166 167 168 169
///
// QtOptionsPage
///

QtOptionsPage::QtOptionsPage()
dt's avatar
dt committed
170
    : m_widget(0)
171
{
hjk's avatar
hjk committed
172
    setId(Constants::QTVERSION_SETTINGS_PAGE_ID);
173
    setDisplayName(QCoreApplication::translate("Qt4ProjectManager", Constants::QTVERSION_SETTINGS_PAGE_NAME));
hjk's avatar
hjk committed
174
    setCategory(ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_CATEGORY);
175 176 177
    setDisplayCategory(QCoreApplication::translate("ProjectExplorer",
        ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_TR_CATEGORY));
    setCategoryIcon(QLatin1String(ProjectExplorer::Constants::PROJECTEXPLORER_SETTINGS_CATEGORY_ICON));
178 179
}

180
QWidget *QtOptionsPage::widget()
181
{
182 183
    if (!m_widget)
        m_widget = new QtOptionsPageWidget;
184 185 186 187 188
    return m_widget;
}

void QtOptionsPage::apply()
{
189 190
    if (!m_widget) // page was never shown
        return;
191
    m_widget->apply();
192 193
}

194
void QtOptionsPage::finish()
195
{
196
    delete m_widget;
197 198
}

199
//-----------------------------------------------------
200 201


202
QtOptionsPageWidget::QtOptionsPageWidget(QWidget *parent)
203 204
    : QWidget(parent)
    , m_specifyNameString(tr("<specify a name>"))
205
    , m_ui(new Internal::Ui::QtVersionManager())
206 207
    , m_versionUi(new Internal::Ui::QtVersionInfo())
    , m_debuggingHelperUi(new Internal::Ui::DebuggingHelper())
208
    , m_infoBrowser(new QTextBrowser)
209 210
    , m_invalidVersionIcon(QLatin1String(Core::Constants::ICON_ERROR))
    , m_warningVersionIcon(QLatin1String(Core::Constants::ICON_WARNING))
dt's avatar
dt committed
211
    , m_configurationWidget(0)
212 213
    , m_autoItem(0)
    , m_manualItem(0)
214
{
215 216
    QWidget *versionInfoWidget = new QWidget();
    m_versionUi->setupUi(versionInfoWidget);
217
    m_versionUi->editPathPushButton->setText(PathChooser::browseButtonLabel());
218

219 220 221
    QWidget *debuggingHelperDetailsWidget = new QWidget();
    m_debuggingHelperUi->setupUi(debuggingHelperDetailsWidget);

222
    m_ui->setupUi(this);
223

224 225
    m_infoBrowser->setOpenLinks(false);
    m_infoBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction);
226 227
    connect(m_infoBrowser, &QTextBrowser::anchorClicked,
            this, &QtOptionsPageWidget::infoAnchorClicked);
228
    m_ui->infoWidget->setWidget(m_infoBrowser);
229 230
    connect(m_ui->infoWidget, &DetailsWidget::expanded,
            this, &QtOptionsPageWidget::setInfoWidgetVisibility);
231

232
    m_ui->versionInfoWidget->setWidget(versionInfoWidget);
hjk's avatar
hjk committed
233
    m_ui->versionInfoWidget->setState(DetailsWidget::NoSummary);
234 235

    m_ui->debuggingHelperWidget->setWidget(debuggingHelperDetailsWidget);
236 237
    connect(m_ui->debuggingHelperWidget, &DetailsWidget::expanded,
            this, &QtOptionsPageWidget::setInfoWidgetVisibility);
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    auto rootItem = new QtVersionItem(QLatin1String("root"));
    m_autoItem = new QtVersionItem(tr("Auto-detected"));
    rootItem->appendChild(m_autoItem);
    m_manualItem = new QtVersionItem(tr("Manual"));
    rootItem->appendChild(m_manualItem);

    m_model = new TreeModel(rootItem);
    m_model->setHeader({tr("Name"), tr("qmake Location"), tr("Type")});

    m_filterModel = new QSortFilterProxyModel(this);
    m_filterModel->setSourceModel(m_model);
    m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);

    m_ui->qtdirList->setModel(m_filterModel);
    m_ui->qtdirList->setSortingEnabled(true);

    m_ui->qtdirList->setFirstColumnSpanned(0, QModelIndex(), true);
    m_ui->qtdirList->setFirstColumnSpanned(1, QModelIndex(), true);

dt_'s avatar
dt_ committed
258
    m_ui->qtdirList->header()->setStretchLastSection(false);
259 260
    m_ui->qtdirList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
    m_ui->qtdirList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
dt_'s avatar
dt_ committed
261
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
262
    m_ui->qtdirList->sortByColumn(0, Qt::AscendingOrder);
263

264
    QList<int> additions = transform(QtVersionManager::versions(), &BaseQtVersion::uniqueId);
265 266

    updateQtVersions(additions, QList<int>(), QList<int>());
267 268

    m_ui->qtdirList->expandAll();
269

270 271
    connect(m_versionUi->nameEdit, &QLineEdit::textEdited,
            this, &QtOptionsPageWidget::updateCurrentQtName);
272

273 274
    connect(m_versionUi->editPathPushButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::editPath);
dt_'s avatar
dt_ committed
275

276 277 278 279
    connect(m_ui->addButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::addQtDir);
    connect(m_ui->delButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::removeQtDir);
280

281
    connect(m_ui->qtdirList->selectionModel(), &QItemSelectionModel::currentChanged,
282
            this, &QtOptionsPageWidget::versionChanged);
283

284 285 286 287
    connect(m_debuggingHelperUi->rebuildButton, &QAbstractButton::clicked,
            this, [this]() { buildDebuggingHelper(); });
    connect(m_debuggingHelperUi->qmlDumpBuildButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::buildQmlDump);
288

289 290 291 292
    connect(m_debuggingHelperUi->showLogButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::slotShowDebuggingBuildLog);
    connect(m_debuggingHelperUi->toolChainComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
            this, &QtOptionsPageWidget::selectedToolChainChanged);
293

294 295
    connect(m_ui->cleanUpButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::cleanUpQtVersions);
dt's avatar
dt committed
296 297
    userChangedCurrentVersion();
    updateCleanUpButton();
298

299 300
    connect(QtVersionManager::instance(), &QtVersionManager::dumpUpdatedFor,
            this, &QtOptionsPageWidget::qtVersionsDumpUpdated);
301

302 303
    connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
            this, &QtOptionsPageWidget::updateQtVersions);
304

305 306
    connect(ProjectExplorer::ToolChainManager::instance(), &ToolChainManager::toolChainsChanged,
            this, &QtOptionsPageWidget::toolChainsUpdated);
hjk's avatar
hjk committed
307 308

    auto chooser = new Core::VariableChooser(this);
309
    chooser->addSupportedWidget(m_versionUi->nameEdit, "Qt:Name");
hjk's avatar
hjk committed
310 311 312 313 314
    chooser->addMacroExpanderProvider(
        [this]() -> Utils::MacroExpander * {
            BaseQtVersion *version = currentVersion();
            return version ? version->macroExpander() : 0;
        });
315 316
}

dt's avatar
dt committed
317
BaseQtVersion *QtOptionsPageWidget::currentVersion() const
318
{
319 320 321 322
    QtVersionItem *item = currentItem();
    if (!item)
        return 0;
    return item->version();
323
}
324

325
QtVersionItem *QtOptionsPageWidget::currentItem() const
326
{
327 328 329 330
    QModelIndex idx = m_ui->qtdirList->selectionModel()->currentIndex();
    QModelIndex sourceIdx = m_filterModel->mapToSource(idx);
    QtVersionItem *item = static_cast<QtVersionItem *>(m_model->itemForIndex(sourceIdx));
    return item;
331
}
332

333
// Update with results of terminated helper build
334
void QtOptionsPageWidget::debuggingHelperBuildFinished(int qtVersionId, const QString &output, DebuggingHelperBuildTask::Tools tools)
335
{
336 337 338 339 340 341 342 343
    auto findItem = [qtVersionId](Utils::TreeItem *parent) {
        foreach (Utils::TreeItem *child, parent->children()) {
            auto item = static_cast<QtVersionItem *>(child);
            if (item->version()->uniqueId() == qtVersionId)
                return item;
        }
        return (QtVersionItem *)nullptr;
    };
344

345 346 347
    QtVersionItem *item = findItem(m_manualItem);
    if (!item)
        item = findItem(m_autoItem);
348

349 350 351 352 353
    if (!item)
        return;


    DebuggingHelperBuildTask::Tools buildFlags = item->buildFlags();
354
    buildFlags &= ~tools;
355 356
    item->setBuildFlags(buildFlags);
    item->setBuildLog(output);
357

358 359
    bool success = true;
    if (tools & DebuggingHelperBuildTask::QmlDump)
360
        success &= item->version()->hasQmlDump();
361

362 363
    if (!success)
        showDebuggingBuildLog(item);
364 365

    updateDebuggingHelperUi();
366 367
}

368 369
void QtOptionsPageWidget::cleanUpQtVersions()
{
370 371 372 373 374 375 376 377 378 379 380
    QVector<QtVersionItem *> toRemove;
    QString text;

    foreach (Utils::TreeItem *child, m_manualItem->children()) {
        auto item = static_cast<QtVersionItem *>(child);
        if (item->version() && !item->version()->isValid()) {
            toRemove.append(item);
            if (!text.isEmpty())
                text.append(QLatin1String("</li><li>"));
            text.append(item->version()->displayName());
        }
381 382 383 384 385
    }

    if (toRemove.isEmpty())
        return;

386

Friedemann Kleint's avatar
Friedemann Kleint committed
387
    if (QMessageBox::warning(0, tr("Remove Invalid Qt Versions"),
388 389
                             tr("Do you want to remove all invalid Qt Versions?<br>"
                                "<ul><li>%1</li></ul><br>"
390
                                "will be removed.").arg(text),
391 392 393
                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
        return;

394 395 396 397
    foreach (QtVersionItem *item, toRemove) {
        m_model->takeItem(item);
        delete item->version();
        delete item;
398
    }
399

dt's avatar
dt committed
400
    updateCleanUpButton();
401 402
}

403 404
void QtOptionsPageWidget::toolChainsUpdated()
{
405 406 407 408 409 410 411 412
    auto update = [this](Utils::TreeItem *parent) {
        foreach (Utils::TreeItem *child, parent->children()) {
            if (child == currentItem()) {
                updateDescriptionLabel();
                updateDebuggingHelperUi();
            } else {
                updateVersionItem(static_cast<QtVersionItem *>(child));
            }
413
        }
414 415 416 417
    };

    update(m_autoItem);
    update(m_manualItem);
418 419
}

420 421
void QtOptionsPageWidget::selectedToolChainChanged(int comboIndex)
{
422 423
    QtVersionItem *item = currentItem();
    if (!item)
424 425
        return;

426
    QByteArray toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(comboIndex).toByteArray();
427
    item->setToolChainId(toolChainId);
428 429
}

hjk's avatar
hjk committed
430
void QtOptionsPageWidget::qtVersionsDumpUpdated(const FileName &qmakeCommand)
431
{
432 433 434 435 436 437 438 439 440 441 442
    auto recheck = [qmakeCommand](Utils::TreeItem *parent) {
        foreach (Utils::TreeItem *child, parent->children()) {
            auto item = static_cast<QtVersionItem *>(child);
            if (item->version()->qmakeCommand() == qmakeCommand)
                item->version()->recheckDumper();
        }
    };

    recheck(m_autoItem);
    recheck(m_manualItem);

443 444
    if (currentVersion()
            && currentVersion()->qmakeCommand() == qmakeCommand) {
445 446 447 448 449 450
        updateWidgets();
        updateDescriptionLabel();
        updateDebuggingHelperUi();
    }
}

451
void QtOptionsPageWidget::setInfoWidgetVisibility()
452
{
453 454 455 456
    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);
457 458
}

459 460 461 462 463
void QtOptionsPageWidget::infoAnchorClicked(const QUrl &url)
{
    QDesktopServices::openUrl(url);
}

464 465 466 467 468 469 470 471
QtOptionsPageWidget::ValidityInfo QtOptionsPageWidget::validInformation(const BaseQtVersion *version)
{
    ValidityInfo info;
    info.icon = m_validVersionIcon;

    if (!version)
        return info;

472
    info.description = tr("Qt version %1 for %2").arg(version->qtVersionString(), version->description());
473 474 475 476 477 478 479 480 481
    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
482 483 484
    foreach (const Abi &abi, version->qtAbis()) {
        if (ToolChainManager::findToolChains(abi).isEmpty())
            missingToolChains.append(abi.toString());
485 486 487
        ++abiCount;
    }

488
    bool useable = true;
489
    QStringList warnings;
490 491 492
    if (!isNameUnique(version))
        warnings << tr("Display Name is not unique.");

493 494 495
    if (!missingToolChains.isEmpty()) {
        if (missingToolChains.count() == abiCount) {
            // Yes, this Qt version can't be used at all!
496
            info.message = tr("No compiler can produce code for this Qt version. Please define one or more compilers.");
497 498 499 500
            info.icon = m_invalidVersionIcon;
            useable = false;
        } else {
            // Yes, some ABIs are unsupported
501
            warnings << tr("Not all possible target environments can be supported due to missing compilers.");
502 503 504 505
            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;
        }
506
    }
507 508

    if (useable) {
509 510
        warnings += version->warningReason();
        if (!warnings.isEmpty()) {
hjk's avatar
hjk committed
511
            info.message = warnings.join(QLatin1Char('\n'));
512 513 514 515
            info.icon = m_warningVersionIcon;
        }
    }

516 517 518
    return info;
}

hjk's avatar
hjk committed
519
QList<ToolChain*> QtOptionsPageWidget::toolChains(const BaseQtVersion *version)
520
{
521
    QList<ToolChain*> toolChains;
522
    if (!version)
523 524
        return toolChains;

525
    QSet<QByteArray> ids;
526 527 528 529 530 531 532 533
    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);
        }
    }
534

535
    return toolChains;
536 537
}

538
QByteArray QtOptionsPageWidget::defaultToolChainId(const BaseQtVersion *version)
539
{
hjk's avatar
hjk committed
540
    QList<ToolChain*> possibleToolChains = toolChains(version);
541 542
    if (!possibleToolChains.isEmpty())
        return possibleToolChains.first()->id();
543
    return QByteArray();
544 545
}

546 547 548
bool QtOptionsPageWidget::isNameUnique(const BaseQtVersion *version)
{
    const QString name = version->displayName().trimmed();
549 550 551 552 553 554 555 556 557 558 559 560 561

    auto isUnique = [name, version](Utils::TreeItem *parent) {
        foreach (Utils::TreeItem *child, parent->children()) {
            auto item = static_cast<QtVersionItem *>(child);
            if (item->version() == version)
                continue;
            if (item->version()->displayName().trimmed() == name)
                return false;
        }
        return true;
    };

    return isUnique(m_manualItem) && isUnique(m_autoItem);
562 563
}

564
void QtOptionsPageWidget::updateVersionItem(QtVersionItem *item)
565
{
566 567 568 569 570 571 572 573
    if (!item)
        return;
    if (!item->version())
        return;

    const ValidityInfo info = validInformation(item->version());
    item->update();
    item->setIcon(info.icon);
574 575
}

576
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
577
{
578 579 580 581 582
    QtVersionItem *item = currentItem();
    if (!item)
        return;

    if (!item->version())
583 584
        return;

585 586 587
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

588
    DebuggingHelperBuildTask::Tools buildFlags = item->buildFlags();
589
    buildFlags |= tools;
590
    item->setBuildFlags(buildFlags);
591

592
    updateDebuggingHelperUi();
593

594
    // Run a debugging helper build task in the background.
595 596
    QByteArray toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(
                m_debuggingHelperUi->toolChainComboBox->currentIndex()).toByteArray();
hjk's avatar
hjk committed
597
    ToolChain *toolChain = ToolChainManager::findToolChain(toolChainId);
598 599 600
    if (!toolChain)
        return;

601
    DebuggingHelperBuildTask *buildTask = new DebuggingHelperBuildTask(item->version(), toolChain, tools);
602 603
    // Don't open General Messages pane with errors
    buildTask->showOutputOnError(false);
604 605
    connect(buildTask, SIGNAL(finished(int,QString,DebuggingHelperBuildTask::Tools)),
            this, SLOT(debuggingHelperBuildFinished(int,QString,DebuggingHelperBuildTask::Tools)),
606
            Qt::QueuedConnection);
607
    QFuture<void> task = QtConcurrent::run(&DebuggingHelperBuildTask::run, buildTask);
608
    const QString taskName = tr("Building Helpers");
609

610
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
611
}
612 613 614 615 616 617

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

618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
// 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)
635
{
636 637 638 639
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
640

641 642
void QtOptionsPageWidget::slotShowDebuggingBuildLog()
{
643 644
    if (const QtVersionItem *item = currentItem())
        showDebuggingBuildLog(item);
645 646
}

647
void QtOptionsPageWidget::showDebuggingBuildLog(const QtVersionItem *item)
648
{
649 650
    BaseQtVersion *version = item->version();
    if (!version)
651
        return;
652
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
653 654
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(version->displayName()));
    dialog->setText(item->buildLog());
655
    dialog->show();
656 657
}

658 659 660
void QtOptionsPageWidget::updateQtVersions(const QList<int> &additions, const QList<int> &removals,
                                           const QList<int> &changes)
{
661
    QList<QtVersionItem *> toRemove;
662 663 664
    QList<int> toAdd = additions;

    // Generate list of all existing items:
665
    QList<QtVersionItem *> itemList;
666
    for (int i = 0; i < m_autoItem->childCount(); ++i)
667
        itemList.append(static_cast<QtVersionItem *>(m_autoItem->child(i)));
668
    for (int i = 0; i < m_manualItem->childCount(); ++i)
669
        itemList.append(static_cast<QtVersionItem *>(m_manualItem->child(i)));
670 671

    // Find existing items to remove/change:
672 673
    foreach (QtVersionItem *item, itemList) {
        int id = item->uniqueId();
674 675 676 677 678 679 680 681 682 683 684 685 686
        if (removals.contains(id)) {
            toRemove.append(item);
            continue;
        }

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

    // Remove changed/removed items:
687 688 689
    foreach (QtVersionItem *item, toRemove) {
        m_model->takeItem(item);
        delete item->version();
690 691 692 693 694
        delete item;
    }

    // Add changed/added items:
    foreach (int a, toAdd) {
hjk's avatar
hjk committed
695
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
696
        auto *item = new QtVersionItem(version);
697

698
        item->setToolChainId(defaultToolChainId(version));
699 700

        // Insert in the right place:
701 702
        Utils::TreeItem *parent = version->isAutodetected()? m_autoItem : m_manualItem;
        parent->appendChild(item);
703
    }
704

705 706 707 708 709 710 711
    auto update = [this](Utils::TreeItem *parent) {
        foreach (Utils::TreeItem *child, parent->children())
            updateVersionItem(static_cast<QtVersionItem *>(child));
    };

    update(m_autoItem);
    update(m_manualItem);
712 713
}

714
QtOptionsPageWidget::~QtOptionsPageWidget()
715 716
{
    delete m_ui;
dt's avatar
dt committed
717 718
    delete m_versionUi;
    delete m_debuggingHelperUi;
dt's avatar
dt committed
719
    delete m_configurationWidget;
720 721
}

dt_'s avatar
dt_ committed
722 723
void QtOptionsPageWidget::addQtDir()
{
hjk's avatar
hjk committed
724
    FileName qtVersion = FileName::fromString(
Tobias Hunger's avatar
Tobias Hunger committed
725
                QFileDialog::getOpenFileName(this,
Friedemann Kleint's avatar
Friedemann Kleint committed
726
                                             tr("Select a qmake Executable"),
Tobias Hunger's avatar
Tobias Hunger committed
727
                                             QString(),
728
                                             BuildableHelperLibrary::filterForQmakeFileDialog(),
Tobias Hunger's avatar
Tobias Hunger committed
729 730
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt's avatar
dt committed
731 732
    if (qtVersion.isNull())
        return;
733 734 735 736

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

739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
    auto checkAlreadyExists = [qtVersion](Utils::TreeItem *parent) {
        for (int i = 0; i < parent->childCount(); ++i) {
            auto item = static_cast<QtVersionItem *>(parent->childAt(i));
            if (item->version()->qmakeCommand() == qtVersion) {
                return std::make_pair(true, item->version()->displayName());
            }
        }
        return std::make_pair(false, QString());
    };

    bool alreadyExists;
    QString otherName;
    std::tie(alreadyExists, otherName) = checkAlreadyExists(m_autoItem);
    if (!alreadyExists)
        std::tie(alreadyExists, otherName) = checkAlreadyExists(m_manualItem);

    if (alreadyExists) {
dt's avatar
dt committed
756
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
757
        QMessageBox::warning(this, tr("Qt Version Already Known"),
758
                             tr("This Qt version was already registered as \"%1\".")
759
                             .arg(otherName));
760
        return;
dt's avatar
dt committed
761
    }
762

763
    QString error;
764
    BaseQtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error);
dt's avatar
dt committed
765
    if (version) {
766 767 768 769 770 771 772
        auto item = new QtVersionItem(version);
        item->setIcon(version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
        m_manualItem->appendChild(item);
        item->setToolChainId(defaultToolChainId(version));

        QModelIndex source = m_model->indexForItem(item);
        m_ui->qtdirList->setCurrentIndex(m_filterModel->mapFromSource(source)); // should update the rest of the ui
dt's avatar
dt committed
773 774
        m_versionUi->nameEdit->setFocus();
        m_versionUi->nameEdit->selectAll();
775
    } else {
776
        QMessageBox::warning(this, tr("Qmake Not Executable"),
Friedemann Kleint's avatar
Friedemann Kleint committed
777
                             tr("The qmake executable %1 could not be added: %2").arg(qtVersion.toUserOutput()).arg(error));
778
        return;
dt's avatar
dt committed
779 780
    }
    updateCleanUpButton();
781 782
}

783
void QtOptionsPageWidget::removeQtDir()
784
{
785 786
    QtVersionItem *item = currentItem();
    if (!item)
787 788
        return;

789 790
    m_model->takeItem(item);
    delete item->version();
791 792
    delete item;

dt's avatar
dt committed
793
    updateCleanUpButton();
794 795
}

dt_'s avatar
dt_ committed
796 797
void QtOptionsPageWidget::editPath()
{
798
    BaseQtVersion *current = currentVersion();
799
    QString dir = currentVersion()->qmakeCommand().toFileInfo().absolutePath();
hjk's avatar
hjk committed
800
    FileName qtVersion = FileName::fromString(
801
                QFileDialog::getOpenFileName(this,
Robert Loehning's avatar
Robert Loehning committed
802
                                             tr("Select a qmake Executable"),
803
                                             dir,
804
                                             BuildableHelperLibrary::filterForQmakeFileDialog(),
805 806
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt_'s avatar
dt_ committed
807 808 809
    if (qtVersion.isNull())
        return;
    BaseQtVersion *version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion);
Daniel Teske's avatar
Daniel Teske committed
810 811
    if (!version)
        return;
dt_'s avatar
dt_ committed
812 813 814
    // Same type? then replace!
    if (current->type() != version->type()) {
        // not the same type, error out
Friedemann Kleint's avatar
Friedemann Kleint committed
815
        QMessageBox::critical(this, tr("Incompatible Qt Versions"),
Friedemann Kleint's avatar
Friedemann Kleint committed
816
                              tr("The Qt version selected must match the device type."),
dt_'s avatar
dt_ committed
817 818 819 820 821 822
                              QMessageBox::Ok);
        delete version;
        return;
    }
    // same type, replace
    version->setId(current->uniqueId());
823 824
    if (current->unexpandedDisplayName() != current->defaultUnexpandedDisplayName(current->qmakeCommand()))
        version->setUnexpandedDisplayName(current->displayName());
dt_'s avatar
dt_ committed
825 826 827 828
    delete current;

    // Update ui
    userChangedCurrentVersion();
829 830 831 832
    QtVersionItem *item = currentItem();
    item->setVersion(version);
    item->setToolChainId(defaultToolChainId(version));
    item->setIcon(version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
dt_'s avatar
dt_ committed
833 834
}

835
void QtOptionsPageWidget::updateDebuggingHelperUi()
836
{
dt's avatar
dt committed
837
    BaseQtVersion *version = currentVersion();
838
    const QtVersionItem *item = currentItem();
839

hjk's avatar
hjk committed
840
    QList<ToolChain*> toolchains = toolChains(currentVersion());
841 842

    if (!version || !version->isValid() || toolchains.isEmpty()) {
843
        m_ui->debuggingHelperWidget->setVisible(false);
844
    } else {
845 846
        const DebuggingHelperBuildTask::Tools availableTools = DebuggingHelperBuildTask::availableTools(version);
        const bool canBuildQmlDumper = availableTools & DebuggingHelperBuildTask::QmlDump;
847

848
        const bool hasQmlDumper = version->hasQmlDump();
849
        const bool needsQmlDumper = version->needsQmlDump();
850

851 852
        bool isBuildingQmlDumper = false;

853 854
        if (item) {
            DebuggingHelperBuildTask::Tools buildingTools = item->buildFlags();
855 856 857
            isBuildingQmlDumper = buildingTools & DebuggingHelperBuildTask::QmlDump;
        }

858 859
        // get names of tools from labels
        QStringList helperNames;
860
        const QChar colon = QLatin1Char(':');
861
        if (hasQmlDumper)
862
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
863 864 865

        QString status;
        if (helperNames.isEmpty()) {
866
            status = tr("Helpers: None available");
867
        } else {
Friedemann Kleint's avatar
Friedemann Kleint committed
868
            //: %1 is list of tool names.
869
            status = tr("Helpers: %1.").arg(helperNames.join(QLatin1String(", ")));
870 871 872 873
        }

        m_ui->debuggingHelperWidget->setSummaryText(status);

874
        QString qmlDumpStatusText, qmlDumpStatusToolTip;
875
        Qt::TextInteractionFlags qmlDumpStatusTextFlags = Qt::NoTextInteraction;
876
        if (hasQmlDumper) {
877 878
            qmlDumpStatusText = QDir::toNativeSeparators(version->qmlDumpTool(false));
            const QString debugQmlDumpPath = QDir::toNativeSeparators(version->qmlDumpTool(true));
879
            if (qmlDumpStatusText != debugQmlDumpPath) {
880 881
                if (!qmlDumpStatusText.isEmpty()
                        && !debugQmlDumpPath.isEmpty())
882 883 884
                    qmlDumpStatusText += QLatin1String("\n");
                qmlDumpStatusText += debugQmlDumpPath;
            }
885
            qmlDumpStatusTextFlags = Qt::TextSelectableByMouse;
886
        } else {
887 888 889
            if (!needsQmlDumper) {
                qmlDumpStatusText = tr("<i>Not needed.</i>");
            } else if (canBuildQmlDumper) {
890 891 892
                qmlDumpStatusText = tr("<i>Not yet built.</i>");
            } else {
                qmlDumpStatusText = tr("<i>Cannot be compiled.</i>");
893
                QmlDumpTool::canBuild(version, &qmlDumpStatusToolTip);
894
            }
895
        }
896
        m_debuggingHelperUi->qmlDumpStatus->setText(qmlDumpStatusText);
897
        m_debuggingHelperUi->qmlDumpStatus->setTextInteractionFlags(qmlDumpStatusTextFlags);
898
        m_debuggingHelperUi->qmlDumpStatus->setToolTip(qmlDumpStatusToolTip);
899
        m_debuggingHelperUi->qmlDumpBuildButton->setEnabled(canBuildQmlDumper & !isBuildingQmlDumper);
900

hjk's avatar
hjk committed
901
        QList<ToolChain*> toolchains = toolChains(currentVersion());
902
        QByteArray selectedToolChainId = item->toolChainId();