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

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

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

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

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

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

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

65 66 67 68 69
///
// QtOptionsPage
///

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

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

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

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()->setResizeMode(0, QHeaderView::ResizeToContents);
    m_ui->qtdirList->header()->setResizeMode(1, QHeaderView::Stretch);
dt_'s avatar
dt_ committed
144
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
145 146 147 148 149 150 151 152 153 154
    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);

    QList<int> additions;
hjk's avatar
hjk committed
155
    foreach (BaseQtVersion *v, QtVersionManager::versions())
156 157 158
        additions.append(v->uniqueId());

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

    m_ui->qtdirList->expandAll();
161

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

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

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

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

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

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

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

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

196 197
    connect(ProjectExplorer::ToolChainManager::instance(), SIGNAL(toolChainsChanged()),
            this, SLOT(toolChainsUpdated()));
198 199
}

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

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

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

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

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

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

242 243
    bool success = true;
    if (tools & DebuggingHelperBuildTask::QmlDump)
dt's avatar
dt committed
244
        success &= version->hasQmlDump();
245

246 247
    if (!success)
        showDebuggingBuildLog(item);
248 249

    updateDebuggingHelperUi();
250 251
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

324
void QtOptionsPageWidget::setInfoWidgetVisibility()
325
{
326 327 328 329
    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);
330 331
}

332 333 334 335 336
void QtOptionsPageWidget::infoAnchorClicked(const QUrl &url)
{
    QDesktopServices::openUrl(url);
}

337 338 339 340 341 342 343 344
QtOptionsPageWidget::ValidityInfo QtOptionsPageWidget::validInformation(const BaseQtVersion *version)
{
    ValidityInfo info;
    info.icon = m_validVersionIcon;

    if (!version)
        return info;

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

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

    if (useable) {
379 380 381
        warnings += version->warningReason();
        if (!warnings.isEmpty()) {
            info.message = warnings.join(QLatin1String("\n"));
382 383 384 385
            info.icon = m_warningVersionIcon;
        }
    }

386 387 388
    return info;
}

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

405
    return toolChains;
406 407
}

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

416
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
417 418 419 420 421
{
    const int index = currentIndex();
    if (index < 0)
        return;

422 423 424
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

425 426
    QTreeWidgetItem *item = treeItemForIndex(index);
    QTC_ASSERT(item, return);
427

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

dt's avatar
dt committed
433
    BaseQtVersion *version = m_versions.at(index);
434 435 436
    if (!version)
        return;

437
    updateDebuggingHelperUi();
438

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

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

455
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
456
}
457 458 459 460 461 462

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

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

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

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

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
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
541
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
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
        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);
    }
}

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

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

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

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

614 615 616 617 618
    BaseQtVersion *version = Utils::findOr(m_versions,
                                           0,
                                           [&qtVersion](BaseQtVersion *v) {
                                                return v->qmakeCommand() == qtVersion;
                                           });
619
    if (version) {
dt's avatar
dt committed
620
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
621
        QMessageBox::warning(this, tr("Qt Version Already Known"),
622 623 624
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
625
    }
626

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

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

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

    delete item;

dt's avatar
dt committed
658
    BaseQtVersion *version = m_versions.at(index);
659
    m_versions.removeAt(index);
660
    delete version;
dt's avatar
dt committed
661
    updateCleanUpButton();
662 663
}

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

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

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

hjk's avatar
hjk committed
711
    QList<ToolChain*> toolchains = toolChains(currentVersion());
712 713

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

719
        const bool hasQmlDumper = version->hasQmlDump();
720
        const bool needsQmlDumper = version->needsQmlDump();
721

722 723 724 725 726 727 728 729
        bool isBuildingQmlDumper = false;

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

787 788 789
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

790 791
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
792 793 794

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
795
        setInfoWidgetVisibility();
796 797 798
    }
}

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

dt's avatar
dt committed
812
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
813
{
dt's avatar
dt committed
814 815 816
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
817 818
}

dt's avatar
dt committed
819
void QtOptionsPageWidget::qtVersionChanged()
820
{
821
    updateDescriptionLabel();
dt's avatar
dt committed
822
    updateDebuggingHelperUi();
823 824 825 826
}

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

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
843
        setInfoWidgetVisibility();
844 845
    } else {
        m_infoBrowser->setHtml(QString());
846
        m_ui->versionInfoWidget->setVisible(false);
847
        m_ui->infoWidget->setVisible(false);
848
        m_ui->debuggingHelperWidget->setVisible(false);
849
    }
850 851
}

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

864 865
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
866
    const int uniqueId = m_versions.at(index)->uniqueId();
867 868 869 870
    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);
871
            if (item->data(0, VersionIdRole).toInt() == uniqueId)
872 873 874 875 876 877
                return item;
        }
    }
    return 0;
}

dt's avatar
dt committed
878
void QtOptionsPageWidget::versionChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *old)
879
{
dt's avatar
dt committed
880 881
    Q_UNUSED(newItem)
    if (old)
882
        fixQtVersionName(indexForTreeItem(old));
dt's avatar