qtoptionspage.cpp 33.5 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Tobias Hunger's avatar
Tobias Hunger 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
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 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.
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 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
****************************************************************************/
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>
44
#include <projectexplorer/toolchainmanager.h>
45
#include <projectexplorer/projectexplorerconstants.h>
46
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
47 48
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
49
#include <utils/runextensions.h>
50
#include <utils/algorithm.h>
51

52 53 54
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
55 56
#include <QTextBrowser>
#include <QDesktopServices>
57

hjk's avatar
hjk committed
58
using namespace ProjectExplorer;
59
using namespace Utils;
60

hjk's avatar
hjk committed
61 62 63 64 65
namespace QtSupport {
namespace Internal {

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

66 67 68 69 70
///
// QtOptionsPage
///

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

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

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

95
void QtOptionsPage::finish()
96
{
97
    delete m_widget;
98 99
}

100
//-----------------------------------------------------
101 102


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

120 121 122
    QWidget *debuggingHelperDetailsWidget = new QWidget();
    m_debuggingHelperUi->setupUi(debuggingHelperDetailsWidget);

123
    m_ui->setupUi(this);
124

125 126 127 128
    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);
129
    connect(m_ui->infoWidget, SIGNAL(expanded(bool)),
130
            this, SLOT(setInfoWidgetVisibility()));
131

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

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

139
    // setup parent items for auto-detected and manual versions
dt_'s avatar
dt_ committed
140
    m_ui->qtdirList->header()->setStretchLastSection(false);
141 142
    m_ui->qtdirList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
    m_ui->qtdirList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
dt_'s avatar
dt_ committed
143
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
144 145 146 147 148 149 150 151 152
    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);

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

    updateQtVersions(additions, QList<int>(), QList<int>());
156 157

    m_ui->qtdirList->expandAll();
158

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

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

165 166 167 168 169
    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
170 171
    connect(m_ui->qtdirList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
            this, SLOT(versionChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
172

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

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

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

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

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

193 194
    connect(ProjectExplorer::ToolChainManager::instance(), SIGNAL(toolChainsChanged()),
            this, SLOT(toolChainsUpdated()));
195 196
}

197
int QtOptionsPageWidget::currentIndex() const
198
{
199 200 201 202
    if (QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        return indexForTreeItem(currentItem);
    return -1;
}
203

dt's avatar
dt committed
204
BaseQtVersion *QtOptionsPageWidget::currentVersion() const
205 206 207
{
    const int currentItemIndex = currentIndex();
    if (currentItemIndex >= 0 && currentItemIndex < m_versions.size())
208
        return m_versions.at(currentItemIndex);
209 210
    return 0;
}
211

dt's avatar
dt committed
212
static inline int findVersionById(const QList<BaseQtVersion *> &l, int id)
213 214 215
{
    const int size = l.size();
    for (int i = 0; i < size; i++)
216
        if (l.at(i)->uniqueId() == id)
217 218 219
            return i;
    return -1;
}
220

221
// Update with results of terminated helper build
222
void QtOptionsPageWidget::debuggingHelperBuildFinished(int qtVersionId, const QString &output, DebuggingHelperBuildTask::Tools tools)
223
{
224
    const int index = findVersionById(m_versions, qtVersionId);
225 226
    if (index == -1)
        return; // Oops, somebody managed to delete the version
227

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

230 231
    // Update item view
    QTreeWidgetItem *item = treeItemForIndex(index);
232 233 234 235 236
    QTC_ASSERT(item, return);
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags &= ~tools;
    item->setData(0, BuildRunningRole,  QVariant::fromValue(buildFlags));
237
    item->setData(0, BuildLogRole, output);
238

239 240
    bool success = true;
    if (tools & DebuggingHelperBuildTask::QmlDump)
dt's avatar
dt committed
241
        success &= version->hasQmlDump();
242

243 244
    if (!success)
        showDebuggingBuildLog(item);
245 246

    updateDebuggingHelperUi();
247 248
}

249 250 251
void QtOptionsPageWidget::cleanUpQtVersions()
{
    QStringList toRemove;
dt's avatar
dt committed
252
    foreach (const BaseQtVersion *v, m_versions) {
253
        if (!v->isValid())
254 255 256 257 258 259
            toRemove.append(v->displayName());
    }

    if (toRemove.isEmpty())
        return;

Friedemann Kleint's avatar
Friedemann Kleint committed
260
    if (QMessageBox::warning(0, tr("Remove Invalid Qt Versions"),
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
                             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
276
    updateCleanUpButton();
277 278
}

279 280 281 282
void QtOptionsPageWidget::toolChainsUpdated()
{
    for (int i = 0; i < m_versions.count(); ++i) {
        QTreeWidgetItem *item = treeItemForIndex(i);
283
        if (item == m_ui->qtdirList->currentItem()) {
284
            updateDescriptionLabel();
285 286
            updateDebuggingHelperUi();
        } else {
287 288 289 290 291 292
            const ValidityInfo info = validInformation(m_versions.at(i));
            item->setIcon(0, info.icon);
        }
    }
}

293 294 295 296 297 298 299 300 301 302 303 304 305 306
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
307
void QtOptionsPageWidget::qtVersionsDumpUpdated(const FileName &qmakeCommand)
308 309 310 311 312
{
    foreach (BaseQtVersion *version, m_versions) {
        if (version->qmakeCommand() == qmakeCommand)
            version->recheckDumper();
    }
313 314
    if (currentVersion()
            && currentVersion()->qmakeCommand() == qmakeCommand) {
315 316 317 318 319 320
        updateWidgets();
        updateDescriptionLabel();
        updateDebuggingHelperUi();
    }
}

321
void QtOptionsPageWidget::setInfoWidgetVisibility()
322
{
323 324 325 326
    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);
327 328
}

329 330 331 332 333
void QtOptionsPageWidget::infoAnchorClicked(const QUrl &url)
{
    QDesktopServices::openUrl(url);
}

334 335 336 337 338 339 340 341
QtOptionsPageWidget::ValidityInfo QtOptionsPageWidget::validInformation(const BaseQtVersion *version)
{
    ValidityInfo info;
    info.icon = m_validVersionIcon;

    if (!version)
        return info;

342
    info.description = tr("Qt version %1 for %2").arg(version->qtVersionString(), version->description());
343 344 345 346 347 348 349 350 351
    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
352 353 354
    foreach (const Abi &abi, version->qtAbis()) {
        if (ToolChainManager::findToolChains(abi).isEmpty())
            missingToolChains.append(abi.toString());
355 356 357
        ++abiCount;
    }

358
    bool useable = true;
359
    QStringList warnings;
360 361 362
    if (!missingToolChains.isEmpty()) {
        if (missingToolChains.count() == abiCount) {
            // Yes, this Qt version can't be used at all!
363
            info.message = tr("No compiler can produce code for this Qt version. Please define one or more compilers.");
364 365 366 367
            info.icon = m_invalidVersionIcon;
            useable = false;
        } else {
            // Yes, some ABIs are unsupported
368
            warnings << tr("Not all possible target environments can be supported due to missing compilers.");
369 370 371 372
            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;
        }
373
    }
374 375

    if (useable) {
376 377
        warnings += version->warningReason();
        if (!warnings.isEmpty()) {
hjk's avatar
hjk committed
378
            info.message = warnings.join(QLatin1Char('\n'));
379 380 381 382
            info.icon = m_warningVersionIcon;
        }
    }

383 384 385
    return info;
}

hjk's avatar
hjk committed
386
QList<ToolChain*> QtOptionsPageWidget::toolChains(const BaseQtVersion *version)
387
{
388
    QList<ToolChain*> toolChains;
389
    if (!version)
390 391 392 393 394 395 396 397 398 399 400
        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);
        }
    }
401

402
    return toolChains;
403 404
}

405 406
QString QtOptionsPageWidget::defaultToolChainId(const BaseQtVersion *version)
{
hjk's avatar
hjk committed
407
    QList<ToolChain*> possibleToolChains = toolChains(version);
408 409 410 411 412
    if (!possibleToolChains.isEmpty())
        return possibleToolChains.first()->id();
    return QString();
}

413
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
414 415 416 417 418
{
    const int index = currentIndex();
    if (index < 0)
        return;

419 420 421
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

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

425 426 427 428
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags |= tools;
    item->setData(0, BuildRunningRole, QVariant::fromValue(buildFlags));
429

dt's avatar
dt committed
430
    BaseQtVersion *version = m_versions.at(index);
431 432 433
    if (!version)
        return;

434
    updateDebuggingHelperUi();
435

436
    // Run a debugging helper build task in the background.
437 438
    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(
                m_debuggingHelperUi->toolChainComboBox->currentIndex()).toString();
hjk's avatar
hjk committed
439
    ToolChain *toolChain = ToolChainManager::findToolChain(toolChainId);
440 441 442 443
    if (!toolChain)
        return;

    DebuggingHelperBuildTask *buildTask = new DebuggingHelperBuildTask(version, toolChain, tools);
444 445
    // Don't open General Messages pane with errors
    buildTask->showOutputOnError(false);
446 447
    connect(buildTask, SIGNAL(finished(int,QString,DebuggingHelperBuildTask::Tools)),
            this, SLOT(debuggingHelperBuildFinished(int,QString,DebuggingHelperBuildTask::Tools)),
448
            Qt::QueuedConnection);
449
    QFuture<void> task = QtConcurrent::run(&DebuggingHelperBuildTask::run, buildTask);
450
    const QString taskName = tr("Building Helpers");
451

452
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
453
}
454 455 456 457 458 459

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

460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
// 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)
477
{
478 479 480 481
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
482

483 484 485 486 487 488 489 490 491
void QtOptionsPageWidget::slotShowDebuggingBuildLog()
{
    if (const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        showDebuggingBuildLog(currentItem);
}

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
492 493
    if (currentItemIndex < 0)
        return;
494
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
495
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
496
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
497
    dialog->show();
498 499
}

500 501 502 503 504 505 506 507 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
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
538
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
        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);
    }
}

565
QtOptionsPageWidget::~QtOptionsPageWidget()
566 567
{
    delete m_ui;
dt's avatar
dt committed
568 569
    delete m_versionUi;
    delete m_debuggingHelperUi;
dt's avatar
dt committed
570
    delete m_configurationWidget;
571
    qDeleteAll(m_versions);
572 573
}

dt_'s avatar
dt_ committed
574
static QString filterForQmakeFileDialog()
575
{
576
    QString filter = QLatin1String("qmake (");
hjk's avatar
hjk committed
577
    const QStringList commands = BuildableHelperLibrary::possibleQMakeCommands();
578 579 580
    for (int i = 0; i < commands.size(); ++i) {
        if (i)
            filter += QLatin1Char(' ');
hjk's avatar
hjk committed
581
        if (HostOsInfo::isMacHost())
582 583
            // work around QTBUG-7739 that prohibits filters that don't start with *
            filter += QLatin1Char('*');
584
        filter += commands.at(i);
hjk's avatar
hjk committed
585
        if (HostOsInfo::isAnyUnixHost() && !HostOsInfo::isMacHost())
586 587 588
            // kde bug, we need at least one wildcard character
            // see QTCREATORBUG-7771
            filter += QLatin1Char('*');
dt's avatar
dt committed
589
    }
590
    filter += QLatin1Char(')');
dt_'s avatar
dt_ committed
591 592
    return filter;
}
593

dt_'s avatar
dt_ committed
594 595
void QtOptionsPageWidget::addQtDir()
{
hjk's avatar
hjk committed
596
    FileName qtVersion = FileName::fromString(
Tobias Hunger's avatar
Tobias Hunger committed
597
                QFileDialog::getOpenFileName(this,
Friedemann Kleint's avatar
Friedemann Kleint committed
598
                                             tr("Select a qmake Executable"),
Tobias Hunger's avatar
Tobias Hunger committed
599 600 601 602
                                             QString(),
                                             filterForQmakeFileDialog(),
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt's avatar
dt committed
603 604
    if (qtVersion.isNull())
        return;
605 606 607 608

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

611 612
    BaseQtVersion *version = Utils::findOrDefault(m_versions,
                                                  Utils::equal(&BaseQtVersion::qmakeCommand, qtVersion));
613
    if (version) {
dt's avatar
dt committed
614
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
615
        QMessageBox::warning(this, tr("Qt Version Already Known"),
616 617 618
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
619
    }
620

621 622
    QString error;
    version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error);
dt's avatar
dt committed
623 624
    if (version) {
        m_versions.append(version);
625

dt's avatar
dt committed
626 627
        QTreeWidgetItem *item = new QTreeWidgetItem(m_ui->qtdirList->topLevelItem(1));
        item->setText(0, version->displayName());
628
        item->setText(1, version->qmakeCommand().toUserOutput());
dt's avatar
dt committed
629
        item->setData(0, VersionIdRole, version->uniqueId());
630
        item->setData(0, ToolChainIdRole, defaultToolChainId(version));
dt's avatar
dt committed
631 632 633 634
        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();
635
    } else {
636
        QMessageBox::warning(this, tr("Qmake Not Executable"),
Friedemann Kleint's avatar
Friedemann Kleint committed
637
                             tr("The qmake executable %1 could not be added: %2").arg(qtVersion.toUserOutput()).arg(error));
638
        return;
dt's avatar
dt committed
639 640
    }
    updateCleanUpButton();
641 642
}

643
void QtOptionsPageWidget::removeQtDir()
644 645
{
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
646
    int index = indexForTreeItem(item);
647 648 649 650 651
    if (index < 0)
        return;

    delete item;

dt's avatar
dt committed
652
    BaseQtVersion *version = m_versions.at(index);
653
    m_versions.removeAt(index);
654
    delete version;
dt's avatar
dt committed
655
    updateCleanUpButton();
656 657
}

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

    // Update ui
    userChangedCurrentVersion();
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
    item->setText(0, version->displayName());
694
    item->setText(1, version->qmakeCommand().toUserOutput());
dt_'s avatar
dt_ committed
695
    item->setData(0, VersionIdRole, version->uniqueId());
696
    item->setData(0, ToolChainIdRole, defaultToolChainId(version));
dt_'s avatar
dt_ committed
697 698 699
    item->setIcon(0, version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
}

700
void QtOptionsPageWidget::updateDebuggingHelperUi()
701
{
dt's avatar
dt committed
702
    BaseQtVersion *version = currentVersion();
703
    const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem();
704

hjk's avatar
hjk committed
705
    QList<ToolChain*> toolchains = toolChains(currentVersion());
706 707

    if (!version || !version->isValid() || toolchains.isEmpty()) {
708
        m_ui->debuggingHelperWidget->setVisible(false);
709
    } else {
710 711
        const DebuggingHelperBuildTask::Tools availableTools = DebuggingHelperBuildTask::availableTools(version);
        const bool canBuildQmlDumper = availableTools & DebuggingHelperBuildTask::QmlDump;
712

713
        const bool hasQmlDumper = version->hasQmlDump();
714
        const bool needsQmlDumper = version->needsQmlDump();
715

716 717 718 719 720 721 722 723
        bool isBuildingQmlDumper = false;

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

724 725
        // get names of tools from labels
        QStringList helperNames;
726
        const QChar colon = QLatin1Char(':');
727
        if (hasQmlDumper)
728
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
729 730 731

        QString status;
        if (helperNames.isEmpty()) {
732
            status = tr("Helpers: None available");
733
        } else {
Friedemann Kleint's avatar
Friedemann Kleint committed
734
            //: %1 is list of tool names.
735
            status = tr("Helpers: %1.").arg(helperNames.join(QLatin1String(", ")));
736 737 738 739
        }

        m_ui->debuggingHelperWidget->setSummaryText(status);

740
        QString qmlDumpStatusText, qmlDumpStatusToolTip;
741
        Qt::TextInteractionFlags qmlDumpStatusTextFlags = Qt::NoTextInteraction;
742
        if (hasQmlDumper) {
743 744
            qmlDumpStatusText = QDir::toNativeSeparators(version->qmlDumpTool(false));
            const QString debugQmlDumpPath = QDir::toNativeSeparators(version->qmlDumpTool(true));
745
            if (qmlDumpStatusText != debugQmlDumpPath) {
746 747
                if (!qmlDumpStatusText.isEmpty()
                        && !debugQmlDumpPath.isEmpty())
748 749 750
                    qmlDumpStatusText += QLatin1String("\n");
                qmlDumpStatusText += debugQmlDumpPath;
            }
751
            qmlDumpStatusTextFlags = Qt::TextSelectableByMouse;
752
        } else {
753 754 755
            if (!needsQmlDumper) {
                qmlDumpStatusText = tr("<i>Not needed.</i>");
            } else if (canBuildQmlDumper) {
756 757 758
                qmlDumpStatusText = tr("<i>Not yet built.</i>");
            } else {
                qmlDumpStatusText = tr("<i>Cannot be compiled.</i>");
759
                QmlDumpTool::canBuild(version, &qmlDumpStatusToolTip);
760
            }
761
        }
762
        m_debuggingHelperUi->qmlDumpStatus->setText(qmlDumpStatusText);
763
        m_debuggingHelperUi->qmlDumpStatus->setTextInteractionFlags(qmlDumpStatusTextFlags);
764
        m_debuggingHelperUi->qmlDumpStatus->setToolTip(qmlDumpStatusToolTip);
765
        m_debuggingHelperUi->qmlDumpBuildButton->setEnabled(canBuildQmlDumper & !isBuildingQmlDumper);
766

hjk's avatar
hjk committed
767
        QList<ToolChain*> toolchains = toolChains(currentVersion());
768
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
769 770 771 772 773 774 775 776 777 778 779 780
        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);
        }

781 782 783
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

784 785
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
786 787 788

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
789
        setInfoWidgetVisibility();
790 791 792
    }
}

Friedemann Kleint's avatar
Friedemann Kleint committed
793
// To be called if a Qt version was removed or added
dt's avatar
dt committed
794
void QtOptionsPageWidget::updateCleanUpButton()
795
{
796
    bool hasInvalidVersion = false;
797 798
    for (int i = 0; i < m_versions.count(); ++i) {
        if (!m_versions.at(i)->isValid()) {
799
            hasInvalidVersion = true;
dt's avatar
dt committed
800
            break;
801 802
        }
    }
803
    m_ui->cleanUpButton->setEnabled(hasInvalidVersion);
804
}
805

dt's avatar
dt committed
806
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
807
{
dt's avatar
dt committed
808 809 810
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
811 812
}

dt's avatar
dt committed
813
void QtOptionsPageWidget::qtVersionChanged()
814
{
815
    updateDescriptionLabel();
dt's avatar
dt committed
816
    updateDebuggingHelperUi();
817 818 819 820
}

void QtOptionsPageWidget::updateDescriptionLabel()
{
821
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
822 823 824 825 826 827 828 829 830 831
    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);
832 833
    if (item)
        item->setIcon(0, info.icon);
834 835 836

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
837
        setInfoWidgetVisibility();
838
    } else {
839
        m_infoBrowser->clear();
840
        m_ui->versionInfoWidget->setVisible(false);
841
        m_ui->infoWidget->setVisible(false);
842
        m_ui->debuggingHelperWidget->setVisible(false);
843
    }
844 845
}

846
int QtOptionsPageWidget::indexForTreeItem(const QTreeWidgetItem *item) const
847 848 849
{
    if (!item || !item->parent())
        return -1;
850
    const int uniqueId = item->data(0, VersionIdRole).toInt();
851 852 853 854 855 856 857
    for (int index = 0; index < m_versions.size(); ++index) {
        if (m_versions.at(index)->uniqueId() == uniqueId)
            return index;
    }
    return -1;
}

858 859
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
860
    const int uniqueId = m_versions.at(index)->uniqueId();
861 862 863 864
    for (int i = 0; i < m_ui->qtdirList->topLevelItemCount(); ++i) {
        QTreeWidgetItem *toplevelItem = m_ui->qtdirList->topLevelItem(i);
        for (int j = 0; j < toplevelItem->childCount(); ++j) {
            QTreeWidgetItem *item = toplevelItem->child(j);
865
            if (item->data(0, VersionIdRole).toInt() == uniqueId)
866 867 868 869 870 871
                return item;
        }
    }
    return 0;
}

dt's avatar
dt committed
872
void QtOptionsPageWidget::versionChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *old)