qtoptionspage.cpp 36.8 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 79
QWidget *QtOptionsPage::createPage(QWidget *parent)
{
80
    m_widget = new QtOptionsPageWidget(parent);
81 82
    if (m_searchKeywords.isEmpty())
        m_searchKeywords = m_widget->searchKeywords();
83 84 85 86 87
    return m_widget;
}

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

92
    m_widget->apply();
93 94
}

95 96 97 98 99
bool QtOptionsPage::matches(const QString &s) const
{
    return m_searchKeywords.contains(s, Qt::CaseInsensitive);
}

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


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

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

123
    m_ui->setupUi(this);
124

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

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

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

139 140
    // setup parent items for auto-detected and manual versions
    m_ui->qtdirList->header()->setResizeMode(QHeaderView::ResizeToContents);
dt_'s avatar
dt_ committed
141 142
    m_ui->qtdirList->header()->setStretchLastSection(false);
    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 180 181
    connect(m_debuggingHelperUi->gdbHelperBuildButton, SIGNAL(clicked()),
            this, SLOT(buildGdbHelper()));
    connect(m_debuggingHelperUi->qmlDumpBuildButton, SIGNAL(clicked()),
            this, SLOT(buildQmlDump()));

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

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

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

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

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

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

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

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

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

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

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

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

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

    updateDebuggingHelperUi();
252 253
}

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

    if (toRemove.isEmpty())
        return;

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

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

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

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

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

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

    if (!version)
        return info;

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

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

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

388 389 390
    return info;
}

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

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

    return toolChains.values();
}

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

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

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

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

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

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

433
    updateDebuggingHelperUi();
434

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    delete item;

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

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

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

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

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

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

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

726 727 728 729 730 731 732 733 734 735
        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;
        }

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

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

        m_ui->debuggingHelperWidget->setSummaryText(status);

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

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

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

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

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

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

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

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

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

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

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

877
int QtOptionsPageWidget::indexForTreeItem(const QTreeWidgetItem *item) const
878 879 880
{
    if (!item || !item->parent())
        return -1;