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 <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
#include <utils/algorithm.h>
49

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

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

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

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

64 65 66 67 68
///
// QtOptionsPage
///

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

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

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

92
    m_widget->apply();
93 94
}

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

100
//-----------------------------------------------------
101 102


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

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

123
    m_ui->setupUi(this);
124

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

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

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

139
    // setup parent items for auto-detected and manual versions
dt_'s avatar
dt_ committed
140
    m_ui->qtdirList->header()->setStretchLastSection(false);
141 142
    m_ui->qtdirList->header()->setResizeMode(0, QHeaderView::ResizeToContents);
    m_ui->qtdirList->header()->setResizeMode(1, QHeaderView::Stretch);
dt_'s avatar
dt_ committed
143
    m_ui->qtdirList->setTextElideMode(Qt::ElideNone);
144 145 146 147 148 149 150 151 152 153
    m_autoItem = new QTreeWidgetItem(m_ui->qtdirList);
    m_autoItem->setText(0, tr("Auto-detected"));
    m_autoItem->setFirstColumnSpanned(true);
    m_autoItem->setFlags(Qt::ItemIsEnabled);
    m_manualItem = new QTreeWidgetItem(m_ui->qtdirList);
    m_manualItem->setText(0, tr("Manual"));
    m_manualItem->setFirstColumnSpanned(true);
    m_manualItem->setFlags(Qt::ItemIsEnabled);

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

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

    m_ui->qtdirList->expandAll();
160

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateDebuggingHelperUi();
249 250
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

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

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

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

    if (!version)
        return info;

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

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

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

385 386 387
    return info;
}

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

404
    return toolChains;
405 406
}

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

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

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

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

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

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

436
    updateDebuggingHelperUi();
437

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

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

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

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

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

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

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
494 495
    if (currentItemIndex < 0)
        return;
496
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
497
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
498
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
499
    dialog->show();
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 532 533 534 535 536 537 538 539
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
540
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
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
        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);
    }
}

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

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

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

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

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

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

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

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

    delete item;

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

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

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

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

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

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

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

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

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

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

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

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

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

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

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

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

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

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

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

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