qtoptionspage.cpp 34.8 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
    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);

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

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

    m_ui->qtdirList->expandAll();
159

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateDebuggingHelperUi();
248 249
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

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

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

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

    if (!version)
        return info;

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

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

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

384 385 386
    return info;
}

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

403
    return toolChains;
404 405
}

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

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

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

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

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

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

435
    updateDebuggingHelperUi();
436

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

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

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

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

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

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

void QtOptionsPageWidget::showDebuggingBuildLog(const QTreeWidgetItem *currentItem)
{
    const int currentItemIndex = indexForTreeItem(currentItem);
493 494
    if (currentItemIndex < 0)
        return;
495
    BuildLogDialog *dialog = new BuildLogDialog(this->window());
496
    dialog->setWindowTitle(tr("Debugging Helper Build Log for \"%1\"").arg(currentItem->text(0)));
497
    dialog->setText(currentItem->data(0, BuildLogRole).toString());
498
    dialog->show();
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 532 533 534 535 536 537 538
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
539
        BaseQtVersion *version = QtVersionManager::version(a)->clone();
540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565
        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);
    }
}

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

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

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

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

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

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

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

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

    delete item;

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

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

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

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

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

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

714
        const bool hasQmlDumper = version->hasQmlDump();
715
        const bool needsQmlDumper = version->needsQmlDump();
716

717 718 719 720 721 722 723 724
        bool isBuildingQmlDumper = false;

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

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

785 786
        const bool canBuild = canBuildQmlDumper;
        const bool isBuilding = isBuildingQmlDumper;
787 788 789

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

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

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

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

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

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

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

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

dt's avatar
dt committed
873
void QtOptionsPageWidget::versionChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *old)
874
{
dt's avatar
dt committed
875 876
    Q_UNUSED(newItem)
    if (old)
877
        fixQtVersionName(indexForTreeItem(old));