qtoptionspage.cpp 34.7 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 <projectexplorer/toolchainmanager.h>
43
#include <projectexplorer/projectexplorerconstants.h>
44
#include <utils/hostosinfo.h>
hjk's avatar
hjk committed
45 46
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
47
#include <utils/runextensions.h>
48

49 50 51
#include <QDir>
#include <QMessageBox>
#include <QFileDialog>
52 53
#include <QTextBrowser>
#include <QDesktopServices>
54

hjk's avatar
hjk committed
55
using namespace ProjectExplorer;
56
using namespace Utils;
57

hjk's avatar
hjk committed
58 59 60 61 62
namespace QtSupport {
namespace Internal {

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

63 64 65 66 67
///
// QtOptionsPage
///

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

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

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

91
    m_widget->apply();
92 93
}

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

99
//-----------------------------------------------------
100 101


102
QtOptionsPageWidget::QtOptionsPageWidget(QWidget *parent)
103 104
    : QWidget(parent)
    , m_specifyNameString(tr("<specify a name>"))
105
    , m_ui(new Internal::Ui::QtVersionManager())
106 107
    , m_versionUi(new Internal::Ui::QtVersionInfo())
    , m_debuggingHelperUi(new Internal::Ui::DebuggingHelper())
108
    , m_infoBrowser(new QTextBrowser)
109 110
    , m_invalidVersionIcon(QLatin1String(":/projectexplorer/images/compile_error.png"))
    , m_warningVersionIcon(QLatin1String(":/projectexplorer/images/compile_warning.png"))
dt's avatar
dt committed
111
    , m_configurationWidget(0)
112 113
    , m_autoItem(0)
    , m_manualItem(0)
114
{
115 116
    QWidget *versionInfoWidget = new QWidget();
    m_versionUi->setupUi(versionInfoWidget);
hjk's avatar
hjk committed
117
    m_versionUi->editPathPushButton->setText(QCoreApplication::translate("Utils::PathChooser", PathChooser::browseButtonLabel));
118

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

122
    m_ui->setupUi(this);
123

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

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

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

138 139
    // setup parent items for auto-detected and manual versions
    m_ui->qtdirList->header()->setResizeMode(QHeaderView::ResizeToContents);
dt_'s avatar
dt_ committed
140 141
    m_ui->qtdirList->header()->setStretchLastSection(false);
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
142 143 144 145 146 147 148 149 150 151
    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
152
    foreach (BaseQtVersion *v, QtVersionManager::versions())
153 154 155
        additions.append(v->uniqueId());

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

    m_ui->qtdirList->expandAll();
158

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

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

165 166 167 168 169
    connect(m_ui->addButton, SIGNAL(clicked()),
            this, SLOT(addQtDir()));
    connect(m_ui->delButton, SIGNAL(clicked()),
            this, SLOT(removeQtDir()));

Robert Loehning's avatar
Robert Loehning committed
170 171
    connect(m_ui->qtdirList, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
            this, SLOT(versionChanged(QTreeWidgetItem*,QTreeWidgetItem*)));
172

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateDebuggingHelperUi();
247 248
}

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

    if (toRemove.isEmpty())
        return;

Friedemann Kleint's avatar
Friedemann Kleint committed
260
    if (QMessageBox::warning(0, tr("Remove Invalid Qt Versions"),
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
                             tr("Do you want to remove all invalid Qt Versions?<br>"
                                "<ul><li>%1</li></ul><br>"
                                "will be removed.").arg(toRemove.join(QLatin1String("</li><li>"))),
                             QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
        return;

    for (int i = m_versions.count() - 1; i >= 0; --i) {
        if (!m_versions.at(i)->isValid()) {
            QTreeWidgetItem *item = treeItemForIndex(i);
            delete item;

            delete m_versions.at(i);
            m_versions.removeAt(i);
        }
    }
dt's avatar
dt committed
276
    updateCleanUpButton();
277 278
}

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

293 294 295 296 297 298 299 300 301 302 303 304 305 306
void QtOptionsPageWidget::selectedToolChainChanged(int comboIndex)
{
    const int index = currentIndex();
    if (index < 0)
        return;

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

    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(comboIndex).toString();

    item->setData(0, ToolChainIdRole, toolChainId);
}

hjk's avatar
hjk committed
307
void QtOptionsPageWidget::qtVersionsDumpUpdated(const FileName &qmakeCommand)
308 309 310 311 312
{
    foreach (BaseQtVersion *version, m_versions) {
        if (version->qmakeCommand() == qmakeCommand)
            version->recheckDumper();
    }
313 314
    if (currentVersion()
            && currentVersion()->qmakeCommand() == qmakeCommand) {
315 316 317 318 319 320
        updateWidgets();
        updateDescriptionLabel();
        updateDebuggingHelperUi();
    }
}

321
void QtOptionsPageWidget::setInfoWidgetVisibility()
322
{
323 324 325 326
    m_ui->versionInfoWidget->setVisible((m_ui->infoWidget->state() == DetailsWidget::Collapsed)
                                        && (m_ui->debuggingHelperWidget->state() == DetailsWidget::Collapsed));
    m_ui->infoWidget->setVisible(m_ui->debuggingHelperWidget->state() == DetailsWidget::Collapsed);
    m_ui->debuggingHelperWidget->setVisible(m_ui->infoWidget->state() == DetailsWidget::Collapsed);
327 328
}

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

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

    if (!version)
        return info;

342
    info.description = tr("Qt version %1 for %2").arg(version->qtVersionString(), version->description());
343 344 345 346 347 348 349 350 351
    if (!version->isValid()) {
        info.icon = m_invalidVersionIcon;
        info.message = version->invalidReason();
        return info;
    }

    // Do we have tool chain issues?
    QStringList missingToolChains;
    int abiCount = 0;
hjk's avatar
hjk committed
352 353 354
    foreach (const Abi &abi, version->qtAbis()) {
        if (ToolChainManager::findToolChains(abi).isEmpty())
            missingToolChains.append(abi.toString());
355 356 357
        ++abiCount;
    }

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

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

383 384 385
    return info;
}

hjk's avatar
hjk committed
386
QList<ToolChain*> QtOptionsPageWidget::toolChains(const BaseQtVersion *version)
387
{
hjk's avatar
hjk committed
388
    QHash<QString,ToolChain*> toolChains;
389 390 391
    if (!version)
        return toolChains.values();

hjk's avatar
hjk committed
392 393
    foreach (const Abi &a, version->qtAbis())
        foreach (ToolChain *tc, ToolChainManager::findToolChains(a))
394 395 396 397 398
            toolChains.insert(tc->id(), tc);

    return toolChains.values();
}

399 400
QString QtOptionsPageWidget::defaultToolChainId(const BaseQtVersion *version)
{
hjk's avatar
hjk committed
401
    QList<ToolChain*> possibleToolChains = toolChains(version);
402 403 404 405 406
    if (!possibleToolChains.isEmpty())
        return possibleToolChains.first()->id();
    return QString();
}

407
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
408 409 410 411 412
{
    const int index = currentIndex();
    if (index < 0)
        return;

413 414 415
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

416 417
    QTreeWidgetItem *item = treeItemForIndex(index);
    QTC_ASSERT(item, return);
418

419 420 421 422
    DebuggingHelperBuildTask::Tools buildFlags
            = item->data(0, BuildRunningRole).value<DebuggingHelperBuildTask::Tools>();
    buildFlags |= tools;
    item->setData(0, BuildRunningRole, QVariant::fromValue(buildFlags));
423

dt's avatar
dt committed
424
    BaseQtVersion *version = m_versions.at(index);
425 426 427
    if (!version)
        return;

428
    updateDebuggingHelperUi();
429

430
    // Run a debugging helper build task in the background.
431 432
    QString toolChainId = m_debuggingHelperUi->toolChainComboBox->itemData(
                m_debuggingHelperUi->toolChainComboBox->currentIndex()).toString();
hjk's avatar
hjk committed
433
    ToolChain *toolChain = ToolChainManager::findToolChain(toolChainId);
434 435 436 437
    if (!toolChain)
        return;

    DebuggingHelperBuildTask *buildTask = new DebuggingHelperBuildTask(version, toolChain, tools);
438 439
    // Don't open General Messages pane with errors
    buildTask->showOutputOnError(false);
440 441
    connect(buildTask, SIGNAL(finished(int,QString,DebuggingHelperBuildTask::Tools)),
            this, SLOT(debuggingHelperBuildFinished(int,QString,DebuggingHelperBuildTask::Tools)),
442
            Qt::QueuedConnection);
443
    QFuture<void> task = QtConcurrent::run(&DebuggingHelperBuildTask::run, buildTask);
444
    const QString taskName = tr("Building helpers");
445

446
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
447
}
448 449 450 451 452 453

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

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
// 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)
471
{
472 473 474 475
    m_ui.log->setPlainText(text); // Show and scroll to bottom
    m_ui.log->moveCursor(QTextCursor::End);
    m_ui.log->ensureCursorVisible();
}
476

477 478 479 480 481 482 483 484 485
void QtOptionsPageWidget::slotShowDebuggingBuildLog()
{
    if (const QTreeWidgetItem *currentItem = m_ui->qtdirList->currentItem())
        showDebuggingBuildLog(currentItem);
}

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
486 487
    if (currentItemIndex < 0)
        return;
488
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
489
    dialog->setWindowTitle(tr("Debugging Helper Build Log for '%1'").arg(currentItem->text(0)));
490
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
491
    dialog->show();
492 493
}

494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
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
532
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
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
        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);
    }
}

559
QtOptionsPageWidget::~QtOptionsPageWidget()
560 561
{
    delete m_ui;
dt's avatar
dt committed
562 563
    delete m_versionUi;
    delete m_debuggingHelperUi;
dt's avatar
dt committed
564
    delete m_configurationWidget;
565
    qDeleteAll(m_versions);
566 567
}

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

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

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

605 606 607 608 609 610 611
    BaseQtVersion *version = 0;
    foreach (BaseQtVersion *v, m_versions) {
        if (v->qmakeCommand() == qtVersion) {
            version = v;
            break;
        }
    }
612
    if (version) {
dt's avatar
dt committed
613
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
614
        QMessageBox::warning(this, tr("Qt Version Already Known"),
615 616 617
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
618
    }
619

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

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

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

    delete item;

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

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

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

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

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

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

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

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

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

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

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

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

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

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

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

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

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

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

857 858
QTreeWidgetItem *QtOptionsPageWidget::treeItemForIndex(int index) const
{
859
    const int uniqueId = m_versions.at(index)->uniqueId();
860 861 862 863
    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);
864
            if (item->data(0, VersionIdRole).toInt() == uniqueId)
865 866 867 868 869 870
                return item;
        }
    }
    return 0;
}

dt's avatar
dt committed
871
void QtOptionsPageWidget::versionChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *old)
872
{
dt's avatar
dt committed
873 874
    Q_UNUSED(newItem)
    if (old)
875
        fixQtVersionName(indexForTreeItem(old));
dt's avatar
dt committed
876 877 878 879 880 881 882 883 884 885
    userChangedCurrentVersion();
}

void QtOptionsPageWidget::updateWidgets()
{
    delete m_configurationWidget;
    m_configurationWidget = 0;
    BaseQtVersion *version = currentVersion();
    if (version) {
        m_versionUi->nameEdit->setText(version->displayName());
886
        m_versionUi->qmakePath->setText(version->qmakeCommand().toUserOutput());
dt's avatar
dt committed
887 888 889
        m_configurationWidget = version->createConfigurationWidget();
        if (m_configurationWidget) {
            m_versionUi->formLayout->addRow(m_configurationWidget);
dt's avatar
dt committed
890
            m_configurationWidget->setEnabled(!version->isAutodetected());