qtoptionspage.cpp 33.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
Tobias Hunger's avatar
Tobias Hunger committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
Tobias Hunger's avatar
Tobias Hunger committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
Tobias Hunger's avatar
Tobias Hunger committed
7
**
hjk's avatar
hjk committed
8 9 10 11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
Tobias Hunger's avatar
Tobias Hunger committed
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
Tobias Hunger's avatar
Tobias Hunger committed
30

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

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

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

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

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

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

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

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

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

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

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

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


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

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

125
    m_ui->setupUi(this);
126

127 128
    m_infoBrowser->setOpenLinks(false);
    m_infoBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction);
129 130
    connect(m_infoBrowser, &QTextBrowser::anchorClicked,
            this, &QtOptionsPageWidget::infoAnchorClicked);
131
    m_ui->infoWidget->setWidget(m_infoBrowser);
132 133
    connect(m_ui->infoWidget, &DetailsWidget::expanded,
            this, &QtOptionsPageWidget::setInfoWidgetVisibility);
134

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

    m_ui->debuggingHelperWidget->setWidget(debuggingHelperDetailsWidget);
139 140
    connect(m_ui->debuggingHelperWidget, &DetailsWidget::expanded,
            this, &QtOptionsPageWidget::setInfoWidgetVisibility);
141

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

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

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

    m_ui->qtdirList->expandAll();
161

162 163
    connect(m_versionUi->nameEdit, &QLineEdit::textEdited,
            this, &QtOptionsPageWidget::updateCurrentQtName);
164

165 166
    connect(m_versionUi->editPathPushButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::editPath);
dt_'s avatar
dt_ committed
167

168 169 170 171
    connect(m_ui->addButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::addQtDir);
    connect(m_ui->delButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::removeQtDir);
172

173 174
    connect(m_ui->qtdirList, &QTreeWidget::currentItemChanged,
            this, &QtOptionsPageWidget::versionChanged);
175

176 177 178 179
    connect(m_debuggingHelperUi->rebuildButton, &QAbstractButton::clicked,
            this, [this]() { buildDebuggingHelper(); });
    connect(m_debuggingHelperUi->qmlDumpBuildButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::buildQmlDump);
180

181 182 183 184
    connect(m_debuggingHelperUi->showLogButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::slotShowDebuggingBuildLog);
    connect(m_debuggingHelperUi->toolChainComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
            this, &QtOptionsPageWidget::selectedToolChainChanged);
185

186 187
    connect(m_ui->cleanUpButton, &QAbstractButton::clicked,
            this, &QtOptionsPageWidget::cleanUpQtVersions);
dt's avatar
dt committed
188 189
    userChangedCurrentVersion();
    updateCleanUpButton();
190

191 192
    connect(QtVersionManager::instance(), &QtVersionManager::dumpUpdatedFor,
            this, &QtOptionsPageWidget::qtVersionsDumpUpdated);
193

194 195
    connect(QtVersionManager::instance(), &QtVersionManager::qtVersionsChanged,
            this, &QtOptionsPageWidget::updateQtVersions);
196

197 198
    connect(ProjectExplorer::ToolChainManager::instance(), &ToolChainManager::toolChainsChanged,
            this, &QtOptionsPageWidget::toolChainsUpdated);
hjk's avatar
hjk committed
199 200

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

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

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

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

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

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

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

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

255 256
    if (!success)
        showDebuggingBuildLog(item);
257 258

    updateDebuggingHelperUi();
259 260
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

333
void QtOptionsPageWidget::setInfoWidgetVisibility()
334
{
335 336 337 338
    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);
339 340
}

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

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

    if (!version)
        return info;

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

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

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

395 396 397
    return info;
}

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

414
    return toolChains;
415 416
}

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

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

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

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

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

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

446
    updateDebuggingHelperUi();
447

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

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

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

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

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

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

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

512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
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
550
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
        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);
    }
}

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

dt_'s avatar
dt_ committed
586 587
void QtOptionsPageWidget::addQtDir()
{
hjk's avatar
hjk committed
588
    FileName qtVersion = FileName::fromString(
Tobias Hunger's avatar
Tobias Hunger committed
589
                QFileDialog::getOpenFileName(this,
Friedemann Kleint's avatar
Friedemann Kleint committed
590
                                             tr("Select a qmake Executable"),
Tobias Hunger's avatar
Tobias Hunger committed
591
                                             QString(),
592
                                             BuildableHelperLibrary::filterForQmakeFileDialog(),
Tobias Hunger's avatar
Tobias Hunger committed
593 594
                                             0,
                                             QFileDialog::DontResolveSymlinks));
dt's avatar
dt committed
595 596
    if (qtVersion.isNull())
        return;
597 598 599 600

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

603 604
    BaseQtVersion *version = Utils::findOrDefault(m_versions,
                                                  Utils::equal(&BaseQtVersion::qmakeCommand, qtVersion));
605
    if (version) {
dt's avatar
dt committed
606
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
607
        QMessageBox::warning(this, tr("Qt Version Already Known"),
608 609 610
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
611
    }
612

613 614
    QString error;
    version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error);
dt's avatar
dt committed
615 616
    if (version) {
        m_versions.append(version);
617

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

635
void QtOptionsPageWidget::removeQtDir()
636 637
{
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
638
    int index = indexForTreeItem(item);
639 640 641 642 643
    if (index < 0)
        return;

    delete item;

dt's avatar
dt committed
644
    BaseQtVersion *version = m_versions.at(index);
645
    m_versions.removeAt(index);
646
    delete version;
dt's avatar
dt committed
647
    updateCleanUpButton();
648 649
}

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

    // Update ui
    userChangedCurrentVersion();
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
    item->setText(0, version->displayName());
686
    item->setText(1, version->qmakeCommand().toUserOutput());
dt_'s avatar
dt_ committed
687
    item->setData(0, VersionIdRole, version->uniqueId());
688
    item->setData(0, ToolChainIdRole, defaultToolChainId(version));
dt_'s avatar
dt_ committed
689 690 691
    item->setIcon(0, version->isValid()? m_validVersionIcon : m_invalidVersionIcon);
}

692
void QtOptionsPageWidget::updateDebuggingHelperUi()
693
{
dt's avatar
dt committed
694
    BaseQtVersion *version = currentVersion();
695
    const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem();
696

hjk's avatar
hjk committed
697
    QList<ToolChain*> toolchains = toolChains(currentVersion());
698 699

    if (!version || !version->isValid() || toolchains.isEmpty()) {
700
        m_ui->debuggingHelperWidget->setVisible(false);
701
    } else {
702 703
        const DebuggingHelperBuildTask::Tools availableTools = DebuggingHelperBuildTask::availableTools(version);
        const bool canBuildQmlDumper = availableTools & DebuggingHelperBuildTask::QmlDump;
704

705
        const bool hasQmlDumper = version->hasQmlDump();
706
        const bool needsQmlDumper = version->needsQmlDump();
707

708 709 710 711 712 713 714 715
        bool isBuildingQmlDumper = false;

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

716 717
        // get names of tools from labels
        QStringList helperNames;
718
        const QChar colon = QLatin1Char(':');
719
        if (hasQmlDumper)
720
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
721 722 723

        QString status;
        if (helperNames.isEmpty()) {
724
            status = tr("Helpers: None available");
725
        } else {
Friedemann Kleint's avatar
Friedemann Kleint committed
726
            //: %1 is list of tool names.
727
            status = tr("Helpers: %1.").arg(helperNames.join(QLatin1String(", ")));
728 729 730 731
        }

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

hjk's avatar
hjk committed
759
        QList<ToolChain*> toolchains = toolChains(currentVersion());
760
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
761 762 763 764 765 766 767 768 769 770 771 772
        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);
        }

773 774 775
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

776 777
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
778 779 780

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
781
        setInfoWidgetVisibility();
782 783 784
    }
}

Friedemann Kleint's avatar
Friedemann Kleint committed
785
// To be called if a Qt version was removed or added
dt's avatar
dt committed
786
void QtOptionsPageWidget::updateCleanUpButton()
787
{
788
    bool hasInvalidVersion = false;
789 790
    for (int i = 0; i < m_versions.count(); ++i) {
        if (!m_versions.at(i)->isValid()) {
791
            hasInvalidVersion = true;
dt's avatar
dt committed
792
            break;
793 794
        }
    }
795
    m_ui->cleanUpButton->setEnabled(hasInvalidVersion);
796
}
797

dt's avatar
dt committed
798
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
799
{
dt's avatar
dt committed
800 801 802
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
803 804
}

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

void QtOptionsPageWidget::updateDescriptionLabel()
{
813
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
814 815 816 817 818 819 820 821 822 823
    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);
824 825
    if (item)
        item->setIcon(0, info.icon);
826 827 828

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
829
        setInfoWidgetVisibility();
830
    } else {
831
        m_infoBrowser->clear();
832
        m_ui->versionInfoWidget->setVisible(false);
833
        m_ui->infoWidget->setVisible(false);
834
        m_ui->debuggingHelperWidget->setVisible(false);
835
    }
836 837
}

838
int QtOptionsPageWidget::indexForTreeItem(const QTreeWidgetItem *item) const
839 840 841
{
    if (!item || !item->parent())
        return -1;
842
    const int uniqueId = item->data(0, VersionIdRole).toInt();
843 844 845 846 847 848 849
    for (int index = 0; index < m_versions.size(); ++index) {
        if (m_versions.at(index)->uniqueId() == uniqueId)
            return index;
    }
    return -1;
}

850 851
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
852
    const int uniqueId = m_versions.at(index)->uniqueId();
853 854 855 856
    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);
857
            if (item->data(0, VersionIdRole).toInt() == uniqueId)
858 859 860 861 862 863
                return item;
        }
    }
    return 0;
}

dt's avatar
dt committed
864
void QtOptionsPageWidget::versionChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *old)
865
{
866 867
    Q_UNUSED(newItem);
    Q_UNUSED(old);
dt's avatar
dt committed
868 869 870 871 872 873 874 875 876
    userChangedCurrentVersion();
}

void QtOptionsPageWidget::updateWidgets()
{
    delete m_configurationWidget;
    m_configurationWidget = 0;
    BaseQtVersion *version = currentVersion();
    if (version) {
Tobias Hunger's avatar