qtoptionspage.cpp 34 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
            updateVersionItem(m_versions.at(i));
300 301 302 303
        }
    }
}

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

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

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

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

    if (!version)
        return info;

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

369
    bool useable = true;
370
    QStringList warnings;
371 372 373
    if (!isNameUnique(version))
        warnings << tr("Display Name is not unique.");

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

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

397 398 399
    return info;
}

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

416
    return toolChains;
417 418
}

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

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
bool QtOptionsPageWidget::isNameUnique(const BaseQtVersion *version)
{
    const QString name = version->displayName().trimmed();
    foreach (const BaseQtVersion *i, m_versions) {
        if (i == version)
            continue;
        if (i->displayName().trimmed() == name)
            return false;
    }
    return true;
}

void QtOptionsPageWidget::updateVersionItem(BaseQtVersion *version)
{
    const ValidityInfo info = validInformation(version);
    QTreeWidgetItem *item = treeItemForIndex(m_versions.indexOf(version));
    item->setText(0, version->displayName());
    item->setText(1, version->qmakeCommand().toUserOutput());
    item->setIcon(0, info.icon);
}

448
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
449 450 451 452 453
{
    const int index = currentIndex();
    if (index < 0)
        return;

454 455 456
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

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

460 461 462 463
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags |= tools;
    item->setData(0, BuildRunningRole, QVariant::fromValue(buildFlags));
464

dt's avatar
dt committed
465
    BaseQtVersion *version = m_versions.at(index);
466 467 468
    if (!version)
        return;

469
    updateDebuggingHelperUi();
470

471
    // Run a debugging helper build task in the background.
472 473
    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(
                m_debuggingHelperUi->toolChainComboBox->currentIndex()).toString();
hjk's avatar
hjk committed
474
    ToolChain *toolChain = ToolChainManager::findToolChain(toolChainId);
475 476 477 478
    if (!toolChain)
        return;

    DebuggingHelperBuildTask *buildTask = new DebuggingHelperBuildTask(version, toolChain, tools);
479 480
    // Don't open General Messages pane with errors
    buildTask->showOutputOnError(false);
481 482
    connect(buildTask, SIGNAL(finished(int,QString,DebuggingHelperBuildTask::Tools)),
            this, SLOT(debuggingHelperBuildFinished(int,QString,DebuggingHelperBuildTask::Tools)),
483
            Qt::QueuedConnection);
484
    QFuture<void> task = QtConcurrent::run(&DebuggingHelperBuildTask::run, buildTask);
485
    const QString taskName = tr("Building Helpers");
486

487
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
488
}
489 490 491 492 493 494

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

495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
// 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)
512
{
513 514 515 516
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
517

518 519 520 521 522 523 524 525 526
void QtOptionsPageWidget::slotShowDebuggingBuildLog()
{
    if (const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        showDebuggingBuildLog(currentItem);
}

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
527 528
    if (currentItemIndex < 0)
        return;
529
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
530
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
531
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
532
    dialog->show();
533 534
}

535 536 537 538 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 565 566 567 568 569 570 571 572
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
573
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
        m_versions.append(version);
        QTreeWidgetItem *item = new QTreeWidgetItem;

        item->setData(0, VersionIdRole, version->uniqueId());
        item->setData(0, ToolChainIdRole, defaultToolChainId(version));

        // 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);
    }
594 595 596 597

    // Only set the icon after all versions are there to make sure all names are known:
    foreach (BaseQtVersion *i, m_versions)
        updateVersionItem(i);
598 599
}

600
QtOptionsPageWidget::~QtOptionsPageWidget()
601 602
{
    delete m_ui;
dt's avatar
dt committed
603 604
    delete m_versionUi;
    delete m_debuggingHelperUi;
dt's avatar
dt committed
605
    delete m_configurationWidget;
606
    qDeleteAll(m_versions);
607 608
}

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

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

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

636 637
    QString error;
    version = QtVersionFactory::createQtVersionFromQMakePath(qtVersion, false, QString(), &error);
dt's avatar
dt committed
638 639
    if (version) {
        m_versions.append(version);
640

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

658
void QtOptionsPageWidget::removeQtDir()
659 660
{
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
661
    int index = indexForTreeItem(item);
662 663 664 665 666
    if (index < 0)
        return;

    delete item;

dt's avatar
dt committed
667
    BaseQtVersion *version = m_versions.at(index);
668
    m_versions.removeAt(index);
669
    delete version;
dt's avatar
dt committed
670
    updateCleanUpButton();
671 672
}

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

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

715
void QtOptionsPageWidget::updateDebuggingHelperUi()
716
{
dt's avatar
dt committed
717
    BaseQtVersion *version = currentVersion();
718
    const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem();
719

hjk's avatar
hjk committed
720
    QList<ToolChain*> toolchains = toolChains(currentVersion());
721 722

    if (!version || !version->isValid() || toolchains.isEmpty()) {
723
        m_ui->debuggingHelperWidget->setVisible(false);
724
    } else {
725 726
        const DebuggingHelperBuildTask::Tools availableTools = DebuggingHelperBuildTask::availableTools(version);
        const bool canBuildQmlDumper = availableTools & DebuggingHelperBuildTask::QmlDump;
727

728
        const bool hasQmlDumper = version->hasQmlDump();
729
        const bool needsQmlDumper = version->needsQmlDump();
730

731 732 733 734 735 736 737 738
        bool isBuildingQmlDumper = false;

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

739 740
        // get names of tools from labels
        QStringList helperNames;
741
        const QChar colon = QLatin1Char(':');
742
        if (hasQmlDumper)
743
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
744 745 746

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

hjk's avatar
hjk committed
782
        QList<ToolChain*> toolchains = toolChains(currentVersion());
783
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
784 785 786 787 788 789 790 791 792 793 794 795
        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);
        }

796 797 798
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

799 800
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
801 802 803

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
804
        setInfoWidgetVisibility();
805 806 807
    }
}

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

dt's avatar
dt committed
821
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
822
{
dt's avatar
dt committed
823 824 825
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
826 827
}

dt's avatar
dt committed
828
void QtOptionsPageWidget::qtVersionChanged()
829
{
830
    updateDescriptionLabel();
dt's avatar
dt committed
831
    updateDebuggingHelperUi();
832 833 834 835
}

void QtOptionsPageWidget::updateDescriptionLabel()
{
836
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
837 838 839 840 841 842 843 844 845 846
    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);
847 848
    if (item)
        item->setIcon(0, info.icon);
849 850 851

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
852
        setInfoWidgetVisibility();
853
    } else {
854
        m_infoBrowser->clear();
855
        m_ui->versionInfoWidget->setVisible(false);
856
        m_ui->infoWidget->setVisible(false);
857
        m_ui->debuggingHelperWidget->setVisible(false);
858
    }
859 860
}

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

873 874
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
875
    const int uniqueId = m_versions.at(index)->uniqueId();
876 877 878 879
    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);