qtoptionspage.cpp 33.8 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/toolchainmanager.h>
46
#include <projectexplorer/projectexplorerconstants.h>
47
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
48 49
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
50
#include <utils/runextensions.h>
51
#include <utils/algorithm.h>
52

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

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

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

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

67 68 69 70 71
///
// QtOptionsPage
///

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

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

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

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

101
//-----------------------------------------------------
102 103


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

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

124
    m_ui->setupUi(this);
125

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

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

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

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

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

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

    m_ui->qtdirList->expandAll();
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateDebuggingHelperUi();
256 257
}

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

    if (toRemove.isEmpty())
        return;

Friedemann Kleint's avatar
Friedemann Kleint committed
269
    if (QMessageBox::warning(0, tr("Remove Invalid Qt Versions"),
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
                             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
285
    updateCleanUpButton();
286 287
}

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

302 303 304 305 306 307 308 309 310 311 312 313 314 315
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
316
void QtOptionsPageWidget::qtVersionsDumpUpdated(const FileName &qmakeCommand)
317 318 319 320 321
{
    foreach (BaseQtVersion *version, m_versions) {
        if (version->qmakeCommand() == qmakeCommand)
            version->recheckDumper();
    }
322 323
    if (currentVersion()
            && currentVersion()->qmakeCommand() == qmakeCommand) {
324 325 326 327 328 329
        updateWidgets();
        updateDescriptionLabel();
        updateDebuggingHelperUi();
    }
}

330
void QtOptionsPageWidget::setInfoWidgetVisibility()
331
{
332 333 334 335
    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);
336 337
}

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

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

    if (!version)
        return info;

351
    info.description = tr("Qt version %1 for %2").arg(version->qtVersionString(), version->description());
352 353 354 355 356 357 358 359 360
    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
361 362 363
    foreach (const Abi &abi, version->qtAbis()) {
        if (ToolChainManager::findToolChains(abi).isEmpty())
            missingToolChains.append(abi.toString());
364 365 366
        ++abiCount;
    }

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

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

392 393 394
    return info;
}

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

411
    return toolChains;
412 413
}

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

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

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

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

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

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

443
    updateDebuggingHelperUi();
444

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

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

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

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

469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
// 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)
486
{
487 488 489 490
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
491

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

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
501 502
    if (currentItemIndex < 0)
        return;
503
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
504
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
505
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
506
    dialog->show();
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 538 539 540 541 542 543 544 545 546
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
547
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
548 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
        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);
    }
}

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

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

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

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

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

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

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

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

    delete item;

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

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

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

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

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

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

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

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

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

hjk's avatar
hjk committed
776
        QList<ToolChain*> toolchains = toolChains(currentVersion());
777
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
778 779 780 781 782 783 784 785 786 787 788 789
        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);
        }

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

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

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

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

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

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

void QtOptionsPageWidget::updateDescriptionLabel()
{
830
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
831 832 833 834 835 836 837 838 839 840
    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);
841 842
    if (item)
        item->setIcon(0, info.icon);
843 844 845

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

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

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