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

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

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

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

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

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

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

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

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

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

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

91
    m_widget->apply();
92 93
}

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

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


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

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

122
    m_ui->setupUi(this);
123

124 125 126 127
    m_infoBrowser->setOpenLinks(false);
    m_infoBrowser->setTextInteractionFlags(Qt::TextBrowserInteraction);
    connect(m_infoBrowser, SIGNAL(anchorClicked(QUrl)), this, SLOT(infoAnchorClicked(QUrl)));
    m_ui->infoWidget->setWidget(m_infoBrowser);
128
    connect(m_ui->infoWidget, SIGNAL(expanded(bool)),
129
            this, SLOT(setInfoWidgetVisibility()));
130

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

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

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

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

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

    m_ui->qtdirList->expandAll();
158

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

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

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

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

173
    connect(m_debuggingHelperUi->rebuildButton, SIGNAL(clicked()),
174
            this, SLOT(buildDebuggingHelper()));
175 176 177 178 179 180
    connect(m_debuggingHelperUi->gdbHelperBuildButton, SIGNAL(clicked()),
            this, SLOT(buildGdbHelper()));
    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::GdbDebugging)
dt's avatar
dt committed
243
        success &= version->hasGdbDebuggingHelper();
244
    if (tools & DebuggingHelperBuildTask::QmlDump)
dt's avatar
dt committed
245
        success &= version->hasQmlDump();
246

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

    updateDebuggingHelperUi();
251 252
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

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

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

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

    if (!version)
        return info;

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

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

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

387 388 389
    return info;
}

hjk's avatar
hjk committed
390
QList<ToolChain*> QtOptionsPageWidget::toolChains(const BaseQtVersion *version)
391
{
hjk's avatar
hjk committed
392
    QHash<QString,ToolChain*> toolChains;
393 394 395
    if (!version)
        return toolChains.values();

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

    return toolChains.values();
}

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

411
void QtOptionsPageWidget::buildDebuggingHelper(DebuggingHelperBuildTask::Tools tools)
412 413 414 415 416
{
    const int index = currentIndex();
    if (index < 0)
        return;

417 418 419
    // remove tools that cannot be build
    tools &= DebuggingHelperBuildTask::availableTools(currentVersion());

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

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

dt's avatar
dt committed
428
    BaseQtVersion *version = m_versions.at(index);
429 430 431
    if (!version)
        return;

432
    updateDebuggingHelperUi();
433

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

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

450
    Core::ProgressManager::addTask(task, taskName, "QmakeProjectManager::BuildHelpers");
451
}
452 453 454 455 456 457 458 459 460 461
void QtOptionsPageWidget::buildGdbHelper()
{
    buildDebuggingHelper(DebuggingHelperBuildTask::GdbDebugging);
}

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 618 619
    BaseQtVersion *version = 0;
    foreach (BaseQtVersion *v, m_versions) {
        if (v->qmakeCommand() == qtVersion) {
            version = v;
            break;
        }
    }
620
    if (version) {
dt's avatar
dt committed
621
        // Already exist
Friedemann Kleint's avatar
Friedemann Kleint committed
622
        QMessageBox::warning(this, tr("Qt Version Already Known"),
623 624 625
                             tr("This Qt version was already registered as \"%1\".")
                             .arg(version->displayName()));
        return;
dt's avatar
dt committed
626
    }
627

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

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

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

    delete item;

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

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

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

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

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

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

721 722
        const bool hasGdbHelper = !version->gdbDebuggingHelperLibrary().isEmpty();
        const bool hasQmlDumper = version->hasQmlDump();
723
        const bool needsQmlDumper = version->needsQmlDump();
724

725 726 727 728 729 730 731 732 733 734
        bool isBuildingGdbHelper = false;
        bool isBuildingQmlDumper = false;

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

735 736
        // get names of tools from labels
        QStringList helperNames;
737
        const QChar colon = QLatin1Char(':');
738
        if (hasGdbHelper)
739
            helperNames << m_debuggingHelperUi->gdbHelperLabel->text().remove(colon);
740
        if (hasQmlDumper)
741
            helperNames << m_debuggingHelperUi->qmlDumpLabel->text().remove(colon);
742 743 744

        QString status;
        if (helperNames.isEmpty()) {
745
            status = tr("Helpers: None available");
746
        } else {
Friedemann Kleint's avatar
Friedemann Kleint committed
747
            //: %1 is list of tool names.
748
            status = tr("Helpers: %1.").arg(helperNames.join(QLatin1String(", ")));
749 750 751 752
        }

        m_ui->debuggingHelperWidget->setSummaryText(status);

753 754
        QString gdbHelperText;
        Qt::TextInteractionFlags gdbHelperTextFlags = Qt::NoTextInteraction;
755
        if (hasGdbHelper) {
756
            gdbHelperText = QDir::toNativeSeparators(version->gdbDebuggingHelperLibrary());
757
            gdbHelperTextFlags = Qt::TextSelectableByMouse;
758
        } else {
759
            if (canBuildGdbHelper)
760
                gdbHelperText =  tr("<i>Not yet built.</i>");
761
            else
762
                gdbHelperText =  tr("<i>Not needed.</i>");
763
        }
764 765
        m_debuggingHelperUi->gdbHelperStatus->setText(gdbHelperText);
        m_debuggingHelperUi->gdbHelperStatus->setTextInteractionFlags(gdbHelperTextFlags);
766
        m_debuggingHelperUi->gdbHelperBuildButton->setEnabled(canBuildGdbHelper && !isBuildingGdbHelper);
767

768
        QString qmlDumpStatusText, qmlDumpStatusToolTip;
769
        Qt::TextInteractionFlags qmlDumpStatusTextFlags = Qt::NoTextInteraction;
770
        if (hasQmlDumper) {
771 772
            qmlDumpStatusText = QDir::toNativeSeparators(version->qmlDumpTool(false));
            const QString debugQmlDumpPath = QDir::toNativeSeparators(version->qmlDumpTool(true));
773
            if (qmlDumpStatusText != debugQmlDumpPath) {
774 775
                if (!qmlDumpStatusText.isEmpty()
                        && !debugQmlDumpPath.isEmpty())
776 777 778
                    qmlDumpStatusText += QLatin1String("\n");
                qmlDumpStatusText += debugQmlDumpPath;
            }
779
            qmlDumpStatusTextFlags = Qt::TextSelectableByMouse;
780
        } else {
781 782 783
            if (!needsQmlDumper) {
                qmlDumpStatusText = tr("<i>Not needed.</i>");
            } else if (canBuildQmlDumper) {
784 785 786
                qmlDumpStatusText = tr("<i>Not yet built.</i>");
            } else {
                qmlDumpStatusText = tr("<i>Cannot be compiled.</i>");
787
                QmlDumpTool::canBuild(version, &qmlDumpStatusToolTip);
788
            }
789
        }
790
        m_debuggingHelperUi->qmlDumpStatus->setText(qmlDumpStatusText);
791
        m_debuggingHelperUi->qmlDumpStatus->setTextInteractionFlags(qmlDumpStatusTextFlags);
792
        m_debuggingHelperUi->qmlDumpStatus->setToolTip(qmlDumpStatusToolTip);
793
        m_debuggingHelperUi->qmlDumpBuildButton->setEnabled(canBuildQmlDumper & !isBuildingQmlDumper);
794

hjk's avatar
hjk committed
795
        QList<ToolChain*> toolchains = toolChains(currentVersion());
796
        QString selectedToolChainId = currentItem->data(0, ToolChainIdRole).toString();
797 798 799 800 801 802 803 804 805 806 807 808
        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);
        }

809 810 811
        const bool hasLog = currentItem && !currentItem->data(0, BuildLogRole).toString().isEmpty();
        m_debuggingHelperUi->showLogButton->setEnabled(hasLog);

812
        const bool canBuild = canBuildGdbHelper
813
                               || canBuildQmlDumper;
814
        const bool isBuilding = isBuildingGdbHelper
815
                                 || isBuildingQmlDumper;
816 817 818

        m_debuggingHelperUi->rebuildButton->setEnabled(canBuild && !isBuilding);
        m_debuggingHelperUi->toolChainComboBox->setEnabled(canBuild && !isBuilding);
819
        setInfoWidgetVisibility();
820 821 822
    }
}

Friedemann Kleint's avatar
Friedemann Kleint committed
823
// To be called if a Qt version was removed or added
dt's avatar
dt committed
824
void QtOptionsPageWidget::updateCleanUpButton()
825
{
826
    bool hasInvalidVersion = false;
827 828
    for (int i = 0; i < m_versions.count(); ++i) {
        if (!m_versions.at(i)->isValid()) {
829
            hasInvalidVersion = true;
dt's avatar
dt committed
830
            break;
831 832
        }
    }
833
    m_ui->cleanUpButton->setEnabled(hasInvalidVersion);
834
}
835

dt's avatar
dt committed
836
void QtOptionsPageWidget::userChangedCurrentVersion()
con's avatar
con committed
837
{
dt's avatar
dt committed
838 839 840
    updateWidgets();
    updateDescriptionLabel();
    updateDebuggingHelperUi();
841 842
}

dt's avatar
dt committed
843
void QtOptionsPageWidget::qtVersionChanged()
844
{
845
    updateDescriptionLabel();
dt's avatar
dt committed
846
    updateDebuggingHelperUi();
847 848 849 850
}

void QtOptionsPageWidget::updateDescriptionLabel()
{
851
    QTreeWidgetItem *item = m_ui->qtdirList->currentItem();
852 853 854 855 856 857 858 859 860 861
    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);
862 863
    if (item)
        item->setIcon(0, info.icon);
864 865 866

    if (version) {
        m_infoBrowser->setHtml(version->toHtml(true));
867
        setInfoWidgetVisibility();
868 869
    } else {
        m_infoBrowser->setHtml(QString());
870
        m_ui->versionInfoWidget->setVisible(false);
871
        m_ui->infoWidget->setVisible(false);
872
        m_ui->debuggingHelperWidget->setVisible(false);
873
    }
874 875
}

876
int QtOptionsPageWidget::indexForTreeItem(const QTreeWidgetItem *item) const