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

    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
{
hjk's avatar
hjk committed
389
    QHash<QString,ToolChain*> toolChains;
390 391 392
    if (!version)
        return toolChains.values();

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

    return toolChains.values();
}

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

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

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

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

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

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

429
    updateDebuggingHelperUi();
430

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    delete item;

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

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

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

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

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

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

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

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

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

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

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

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

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

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

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

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

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

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

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

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

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

void QtOptionsPageWidget::updateWidgets()
{
    delete m_configurationWidget;
    m_configurationWidget = 0;
    BaseQtVersion *version = currentVersion();
    if (version) {
        m_versionUi->nameEdit->setText(version->displayName());
887
        m_versionUi->qmakePath->setText(version->qmakeCommand().toUserOutput());
dt's avatar
dt committed