settingsdialog.cpp 24.1 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3 4
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
12 13 14
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
15
**
16 17 18 19 20 21 22
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
con's avatar
con committed
23
**
hjk's avatar
hjk committed
24
****************************************************************************/
hjk's avatar
hjk committed
25

con's avatar
con committed
26 27
#include "settingsdialog.h"

28
#include <coreplugin/icore.h>
29

30
#include <utils/algorithm.h>
31
#include <utils/hostosinfo.h>
32
#include <utils/fancylineedit.h>
33
#include <utils/qtcassert.h>
34

hjk's avatar
hjk committed
35 36 37
#include <QApplication>
#include <QDialogButtonBox>
#include <QGridLayout>
38 39 40
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
hjk's avatar
hjk committed
41 42
#include <QListView>
#include <QPointer>
43
#include <QPushButton>
44 45
#include <QResizeEvent>
#include <QScrollArea>
46
#include <QScrollBar>
hjk's avatar
hjk committed
47 48
#include <QSettings>
#include <QSortFilterProxyModel>
49 50
#include <QSpacerItem>
#include <QStackedLayout>
hjk's avatar
hjk committed
51
#include <QStyle>
52
#include <QStyledItemDelegate>
53 54

static const char pageKeyC[] = "General/LastPreferencePage";
55
const int categoryIconSize = 24;
56 57 58

namespace Core {
namespace Internal {
con's avatar
con committed
59

hjk's avatar
hjk committed
60
static QPointer<SettingsDialog> m_instance = 0;
61

62 63 64 65 66 67 68 69 70
bool optionsPageLessThan(const IOptionsPage *p1, const IOptionsPage *p2)
{
    if (p1->category() != p2->category())
        return p1->category().alphabeticallyBefore(p2->category());
    return p1->id().alphabeticallyBefore(p2->id());
}

static inline QList<IOptionsPage*> sortedOptionsPages()
{
71
    QList<IOptionsPage*> rc = IOptionsPage::allOptionsPages();
72
    std::stable_sort(rc.begin(), rc.end(), optionsPageLessThan);
73 74 75
    return rc;
}

76 77
// ----------- Category model

hjk's avatar
hjk committed
78 79
class Category
{
Tobias Hunger's avatar
Tobias Hunger committed
80
public:
81 82
    Category() : index(-1), providerPagesCreated(false) { }

83 84
    bool findPageById(const Id id, int *pageIndex) const
    {
Eike Ziller's avatar
Eike Ziller committed
85 86
        *pageIndex = Utils::indexOf(pages, Utils::equal(&IOptionsPage::id, id));
        return *pageIndex != -1;
87 88
    }

hjk's avatar
hjk committed
89
    Id id;
90
    int index;
91
    QString displayName;
92
    QIcon icon;
93 94
    QList<IOptionsPage *> pages;
    QList<IOptionsPageProvider *> providers;
95
    bool providerPagesCreated;
96 97 98 99 100 101 102 103 104
    QTabWidget *tabWidget;
};

class CategoryModel : public QAbstractListModel
{
public:
    CategoryModel(QObject *parent = 0);
    ~CategoryModel();

Eike Ziller's avatar
Eike Ziller committed
105 106
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
107

108 109
    void setPages(const QList<IOptionsPage*> &pages,
                  const QList<IOptionsPageProvider *> &providers);
110
    void ensurePages(Category *category);
111 112 113
    const QList<Category*> &categories() const { return m_categories; }

private:
hjk's avatar
hjk committed
114
    Category *findCategoryById(Id id);
115 116

    QList<Category*> m_categories;
117
    QSet<Id> m_pageIds;
118
    QIcon m_emptyIcon;
119 120 121 122
};

CategoryModel::CategoryModel(QObject *parent)
    : QAbstractListModel(parent)
123
{
124 125 126
    QPixmap empty(categoryIconSize, categoryIconSize);
    empty.fill(Qt::transparent);
    m_emptyIcon = QIcon(empty);
127 128
}

129
CategoryModel::~CategoryModel()
130
{
131
    qDeleteAll(m_categories);
132 133
}

134
int CategoryModel::rowCount(const QModelIndex &parent) const
135
{
136
    return parent.isValid() ? 0 : m_categories.size();
137 138
}

139
QVariant CategoryModel::data(const QModelIndex &index, int role) const
140
{
141 142 143
    switch (role) {
    case Qt::DisplayRole:
        return m_categories.at(index.row())->displayName;
144 145 146 147 148 149
    case Qt::DecorationRole: {
            QIcon icon = m_categories.at(index.row())->icon;
            if (icon.isNull())
                icon = m_emptyIcon;
            return icon;
        }
150 151 152
    }

    return QVariant();
153 154
}

155 156
void CategoryModel::setPages(const QList<IOptionsPage*> &pages,
                             const QList<IOptionsPageProvider *> &providers)
157
{
158 159
    beginResetModel();

160 161 162
    // Clear any previous categories
    qDeleteAll(m_categories);
    m_categories.clear();
163
    m_pageIds.clear();
164 165 166

    // Put the pages in categories
    foreach (IOptionsPage *page, pages) {
167 168 169
        QTC_ASSERT(!m_pageIds.contains(page->id()),
                   qWarning("duplicate options page id '%s'", qPrintable(page->id().toString())));
        m_pageIds.insert(page->id());
hjk's avatar
hjk committed
170
        const Id categoryId = page->category();
171 172 173 174
        Category *category = findCategoryById(categoryId);
        if (!category) {
            category = new Category;
            category->id = categoryId;
175 176 177 178 179
            category->tabWidget = 0;
            category->index = -1;
            m_categories.append(category);
        }
        if (category->displayName.isEmpty())
180
            category->displayName = page->displayCategory();
181
        if (category->icon.isNull())
182
            category->icon = page->categoryIcon();
183 184 185 186
        category->pages.append(page);
    }

    foreach (IOptionsPageProvider *provider, providers) {
hjk's avatar
hjk committed
187
        const Id categoryId = provider->category();
188 189 190 191 192 193
        Category *category = findCategoryById(categoryId);
        if (!category) {
            category = new Category;
            category->id = categoryId;
            category->tabWidget = 0;
            category->index = -1;
194 195
            m_categories.append(category);
        }
196 197 198 199 200
        if (category->displayName.isEmpty())
            category->displayName = provider->displayCategory();
        if (category->icon.isNull())
            category->icon = provider->categoryIcon();
        category->providers.append(provider);
201 202
    }

203 204 205
    Utils::sort(m_categories, [](const Category *c1, const Category *c2) {
       return c1->id.alphabeticallyBefore(c2->id);
    });
206
    endResetModel();
207 208
}

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
void CategoryModel::ensurePages(Category *category)
{
    if (!category->providerPagesCreated) {
        QList<IOptionsPage *> createdPages;
        foreach (const IOptionsPageProvider *provider, category->providers)
            createdPages += provider->pages();

        // check for duplicate ids
        foreach (IOptionsPage *page, createdPages) {
            QTC_ASSERT(!m_pageIds.contains(page->id()),
                       qWarning("duplicate options page id '%s'", qPrintable(page->id().toString())));
        }

        category->pages += createdPages;
        category->providerPagesCreated = true;
224
        std::stable_sort(category->pages.begin(), category->pages.end(), optionsPageLessThan);
225 226 227
    }
}

hjk's avatar
hjk committed
228
Category *CategoryModel::findCategoryById(Id id)
229
{
230 231 232 233 234 235 236
    for (int i = 0; i < m_categories.size(); ++i) {
        Category *category = m_categories.at(i);
        if (category->id == id)
            return category;
    }

    return 0;
237
}
238

239 240 241 242 243 244 245 246
// ----------- Category filter model

/**
 * A filter model that returns true for each category node that has pages that
 * match the search string.
 */
class CategoryFilterModel : public QSortFilterProxyModel
{
247
public:
248 249 250 251
    explicit CategoryFilterModel(QObject *parent = 0)
        : QSortFilterProxyModel(parent)
    {}

252
protected:
Eike Ziller's avatar
Eike Ziller committed
253
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
254 255
};

256
bool CategoryFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
257 258
{
    // Regular contents check, then check page-filter.
259
    if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent))
260
        return true;
261

262
    const QString pattern = filterRegExp().pattern();
263
    const CategoryModel *cm = static_cast<CategoryModel*>(sourceModel());
264 265
    const Category *category = cm->categories().at(sourceRow);
    foreach (const IOptionsPage *page, category->pages) {
266
        if (page->displayCategory().contains(pattern, Qt::CaseInsensitive)
267 268
                || page->displayName().contains(pattern, Qt::CaseInsensitive)
                || page->matches(pattern))
269
            return true;
270
    }
271

272 273 274 275 276 277 278
    if (!category->providerPagesCreated) {
        foreach (const IOptionsPageProvider *provider, category->providers) {
            if (provider->matches(pattern))
                return true;
        }
    }

279
    return false;
280
}
con's avatar
con committed
281

282
// ----------- Category list view
283

284 285 286 287 288 289 290 291 292 293 294 295 296

class CategoryListViewDelegate : public QStyledItemDelegate
{
public:
    CategoryListViewDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QSize size = QStyledItemDelegate::sizeHint(option, index);
        size.setHeight(qMax(size.height(), 32));
        return size;
    }
};

297
/**
298
 * Special version of a QListView that has the width of the first column as
299 300
 * minimum size.
 */
301
class CategoryListView : public QListView
302 303
{
public:
304
    CategoryListView(QWidget *parent = 0) : QListView(parent)
305 306
    {
        setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding);
307
        setItemDelegate(new CategoryListViewDelegate(this));
308
        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
309 310 311 312
    }

    virtual QSize sizeHint() const
    {
313
        int width = sizeHintForColumn(0) + frameWidth() * 2 + 5;
314
        width += verticalScrollBar()->width();
315
        return QSize(width, 100);
316
    }
317 318 319 320 321 322 323 324 325 326

    // QListView installs a event filter on its scrollbars
    virtual bool eventFilter(QObject *obj, QEvent *event)
    {
        if (obj == verticalScrollBar()
                && (event->type() == QEvent::Show
                    || event->type() == QEvent::Hide))
            updateGeometry();
        return QListView::eventFilter(obj, event);
    }
327 328
};

329 330 331 332 333 334 335 336 337 338
// ----------- SmartScrollArea

class SmartScrollArea : public QScrollArea
{
public:
    SmartScrollArea(QWidget *parent = 0)
        : QScrollArea(parent)
    {
        setFrameStyle(QFrame::NoFrame | QFrame::Plain);
        viewport()->setAutoFillBackground(false);
Tobias Hunger's avatar
Tobias Hunger committed
339
        setWidgetResizable(true);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
    }
private:
    void resizeEvent(QResizeEvent *event) override
    {
        QWidget *inner = widget();
        if (inner) {
            int fw = frameWidth() * 2;
            QSize innerSize = event->size() - QSize(fw, fw);
            QSize innerSizeHint = inner->minimumSizeHint();

            if (innerSizeHint.height() > innerSize.height()) { // Widget wants to be bigger than available space
                innerSize.setWidth(innerSize.width() - scrollBarWidth());
                innerSize.setHeight(innerSizeHint.height());
            }
            inner->resize(innerSize);
        }
        QScrollArea::resizeEvent(event);
    }

    QSize minimumSizeHint() const override {
        QWidget *inner = widget();
        if (inner) {
            int fw = frameWidth() * 2;

            QSize minSize = inner->minimumSizeHint();
            minSize += QSize(fw, fw);
            minSize += QSize(scrollBarWidth(), 0);
            minSize.setHeight(qMin(minSize.height(), 450));
            minSize.setWidth(qMin(minSize.width(), 810));
            return minSize;
        }
        return QSize(0, 0);
    }

    bool event(QEvent *event) override {
        if (event->type() == QEvent::LayoutRequest)
            updateGeometry();
        return QScrollArea::event(event);
    }

    int scrollBarWidth() const
    {
        auto that = const_cast<SmartScrollArea *>(this);
        QWidgetList list = that->scrollBarWidgets(Qt::AlignRight);
        if (list.isEmpty())
            return 0;
        return list.first()->sizeHint().width();
    }
};


391 392
// ----------- SettingsDialog

393
SettingsDialog::SettingsDialog(QWidget *parent) :
394
    QDialog(parent),
395
    m_pages(sortedOptionsPages()),
396 397
    m_proxyModel(new CategoryFilterModel(this)),
    m_model(new CategoryModel(this)),
Friedemann Kleint's avatar
Friedemann Kleint committed
398
    m_stackedLayout(new QStackedLayout),
399
    m_filterLineEdit(new Utils::FancyLineEdit),
400
    m_categoryList(new CategoryListView),
401 402
    m_headerLabel(new QLabel),
    m_running(false),
403 404
    m_applied(false),
    m_finished(false)
con's avatar
con committed
405
{
406
    m_applied = false;
407
    m_filterLineEdit->setFiltering(true);
408

Friedemann Kleint's avatar
Friedemann Kleint committed
409
    createGui();
410
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
411 412 413 414
    if (Utils::HostOsInfo::isMacHost())
        setWindowTitle(tr("Preferences"));
    else
        setWindowTitle(tr("Options"));
415

416
    m_model->setPages(m_pages, IOptionsPageProvider::allOptionsPagesProviders());
con's avatar
con committed
417

418
    m_proxyModel->setSourceModel(m_model);
419
    m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
420
    m_categoryList->setIconSize(QSize(categoryIconSize, categoryIconSize));
421 422
    m_categoryList->setModel(m_proxyModel);
    m_categoryList->setSelectionMode(QAbstractItemView::SingleSelection);
423
    m_categoryList->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
424

hjk's avatar
hjk committed
425 426
    connect(m_categoryList->selectionModel(), &QItemSelectionModel::currentRowChanged,
            this, &SettingsDialog::currentChanged);
427

428 429
    // The order of the slot connection matters here, the filter slot
    // opens the matching page after the model has filtered.
hjk's avatar
hjk committed
430 431 432 433
    connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
            m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
    connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
            this, &SettingsDialog::filter);
434 435 436
    m_categoryList->setFocus();
}

437
void SettingsDialog::showPage(const Id pageId)
438 439
{
    // handle the case of "show last page"
440 441
    Id initialPageId = pageId;
    if (!initialPageId.isValid()) {
hjk's avatar
hjk committed
442
        QSettings *settings = ICore::settings();
443
        initialPageId = Id::fromSetting(settings->value(QLatin1String(pageKeyC)));
444 445 446 447
    }

    int initialCategoryIndex = -1;
    int initialPageIndex = -1;
448

449
    const QList<Category*> &categories = m_model->categories();
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
    if (initialPageId.isValid()) {
        // First try categories without lazy items.
        for (int i = 0; i < categories.size(); ++i) {
            Category *category = categories.at(i);
            if (category->providers.isEmpty()) {  // no providers
                if (category->findPageById(initialPageId, &initialPageIndex)) {
                    initialCategoryIndex = i;
                    break;
                }
            }
        }

        if (initialPageIndex == -1) {
            // On failure, expand the remaining items.
            for (int i = 0; i < categories.size(); ++i) {
                Category *category = categories.at(i);
                if (!category->providers.isEmpty()) { // has providers
                    ensureCategoryWidget(category);
                    if (category->findPageById(initialPageId, &initialPageIndex)) {
                        initialCategoryIndex = i;
                        break;
                    }
472
                }
473 474 475
            }
        }
    }
476

477 478
    if (initialPageId.isValid() && initialPageIndex == -1)
        return; // Unknown settings page, probably due to missing plugin.
479

480
    if (initialCategoryIndex != -1) {
481 482 483 484 485
        QModelIndex modelIndex = m_proxyModel->mapFromSource(m_model->index(initialCategoryIndex));
        if (!modelIndex.isValid()) { // filtered out, so clear filter first
            m_filterLineEdit->setText(QString());
            modelIndex = m_proxyModel->mapFromSource(m_model->index(initialCategoryIndex));
        }
486
        m_categoryList->setCurrentIndex(modelIndex);
487 488 489 490
        if (initialPageIndex != -1) {
            if (QTC_GUARD(categories.at(initialCategoryIndex)->tabWidget))
                categories.at(initialCategoryIndex)->tabWidget->setCurrentIndex(initialPageIndex);
        }
con's avatar
con committed
491
    }
Friedemann Kleint's avatar
Friedemann Kleint committed
492 493 494 495 496 497 498 499 500 501 502 503 504 505
}

void SettingsDialog::createGui()
{
    // Header label with large font and a bit of spacing (align with group boxes)
    QFont headerLabelFont = m_headerLabel->font();
    headerLabelFont.setBold(true);
    // Paranoia: Should a font be set in pixels...
    const int pointSize = headerLabelFont.pointSize();
    if (pointSize > 0)
        headerLabelFont.setPointSize(pointSize + 2);
    m_headerLabel->setFont(headerLabelFont);

    QHBoxLayout *headerHLayout = new QHBoxLayout;
506
    const int leftMargin = QApplication::style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
Friedemann Kleint's avatar
Friedemann Kleint committed
507 508 509 510
    headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
    headerHLayout->addWidget(m_headerLabel);

    m_stackedLayout->setMargin(0);
511
    m_stackedLayout->addWidget(new QWidget(this)); // no category selected, for example when filtering
Friedemann Kleint's avatar
Friedemann Kleint committed
512

513 514 515
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok |
                                                       QDialogButtonBox::Apply |
                                                       QDialogButtonBox::Cancel);
hjk's avatar
hjk committed
516 517 518 519 520
    connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked,
            this, &SettingsDialog::apply);

    connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &SettingsDialog::reject);
Friedemann Kleint's avatar
Friedemann Kleint committed
521 522 523 524

    QGridLayout *mainGridLayout = new QGridLayout;
    mainGridLayout->addWidget(m_filterLineEdit, 0, 0, 1, 1);
    mainGridLayout->addLayout(headerHLayout,    0, 1, 1, 1);
525
    mainGridLayout->addWidget(m_categoryList,   1, 0, 1, 1);
Friedemann Kleint's avatar
Friedemann Kleint committed
526
    mainGridLayout->addLayout(m_stackedLayout,  1, 1, 1, 1);
527
    mainGridLayout->addWidget(buttonBox,        2, 0, 1, 2);
Friedemann Kleint's avatar
Friedemann Kleint committed
528 529
    mainGridLayout->setColumnStretch(1, 4);
    setLayout(mainGridLayout);
530 531 532

    buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);

533
    mainGridLayout->setSizeConstraint(QLayout::SetMinimumSize);
con's avatar
con committed
534 535 536 537 538 539
}

SettingsDialog::~SettingsDialog()
{
}

540
void SettingsDialog::showCategory(int index)
con's avatar
con committed
541
{
542
    Category *category = m_model->categories().at(index);
543
    ensureCategoryWidget(category);
544 545 546 547 548 549 550
    // Update current category and page
    m_currentCategory = category->id;
    const int currentTabIndex = category->tabWidget->currentIndex();
    if (currentTabIndex != -1) {
        IOptionsPage *page = category->pages.at(currentTabIndex);
        m_currentPage = page->id();
        m_visitedPages.insert(page);
551
    }
552 553 554 555 556

    m_stackedLayout->setCurrentIndex(category->index);
    m_headerLabel->setText(category->displayName);

    updateEnabledTabs(category, m_filterLineEdit->text());
557 558
}

559 560 561 562 563
void SettingsDialog::ensureCategoryWidget(Category *category)
{
    if (category->tabWidget != 0)
        return;

564
    m_model->ensurePages(category);
565 566 567
    QTabWidget *tabWidget = new QTabWidget;
    for (int j = 0; j < category->pages.size(); ++j) {
        IOptionsPage *page = category->pages.at(j);
568
        QWidget *widget = page->widget();
569
        ICore::setupScreenShooter(page->displayName(), widget);
570 571 572 573
        SmartScrollArea *ssa = new SmartScrollArea(this);
        ssa->setWidget(widget);
        widget->setAutoFillBackground(false);
        tabWidget->addTab(ssa, page->displayName());
574 575
    }

hjk's avatar
hjk committed
576 577
    connect(tabWidget, &QTabWidget::currentChanged,
            this, &SettingsDialog::currentTabChanged);
578 579 580 581 582

    category->tabWidget = tabWidget;
    category->index = m_stackedLayout->addWidget(tabWidget);
}

583 584 585 586
void SettingsDialog::disconnectTabWidgets()
{
    foreach (Category *category, m_model->categories()) {
        if (category->tabWidget)
hjk's avatar
hjk committed
587 588
            disconnect(category->tabWidget, &QTabWidget::currentChanged,
                       this, &SettingsDialog::currentTabChanged);
589 590 591
    }
}

592
void SettingsDialog::updateEnabledTabs(Category *category, const QString &searchText)
593
{
594
    int firstEnabledTab = -1;
595 596 597
    for (int i = 0; i < category->pages.size(); ++i) {
        const IOptionsPage *page = category->pages.at(i);
        const bool enabled = searchText.isEmpty()
hjk's avatar
hjk committed
598
                             || page->category().toString().contains(searchText, Qt::CaseInsensitive)
599 600 601
                             || page->displayName().contains(searchText, Qt::CaseInsensitive)
                             || page->matches(searchText);
        category->tabWidget->setTabEnabled(i, enabled);
602 603 604 605 606 607 608
        if (enabled && firstEnabledTab < 0)
            firstEnabledTab = i;
    }
    if (!category->tabWidget->isTabEnabled(category->tabWidget->currentIndex())
            && firstEnabledTab != -1) {
        // QTabWidget is dumb, so this can happen
        category->tabWidget->setCurrentIndex(firstEnabledTab);
609
    }
610 611
}

612
void SettingsDialog::currentChanged(const QModelIndex &current)
613
{
614
    if (current.isValid()) {
615
        showCategory(m_proxyModel->mapToSource(current).row());
616 617
    } else {
        m_stackedLayout->setCurrentIndex(0);
618
        m_headerLabel->clear();
619
    }
620 621
}

622
void SettingsDialog::currentTabChanged(int index)
623
{
624 625 626 627 628 629 630 631 632 633 634 635
    if (index == -1)
        return;

    const QModelIndex modelIndex = m_proxyModel->mapToSource(m_categoryList->currentIndex());
    if (!modelIndex.isValid())
        return;

    // Remember the current tab and mark it as visited
    const Category *category = m_model->categories().at(modelIndex.row());
    IOptionsPage *page = category->pages.at(index);
    m_currentPage = page->id();
    m_visitedPages.insert(page);
636 637 638 639
}

void SettingsDialog::filter(const QString &text)
{
640 641 642 643 644 645
    // When there is no current index, select the first one when possible
    if (!m_categoryList->currentIndex().isValid() && m_model->rowCount() > 0)
        m_categoryList->setCurrentIndex(m_proxyModel->index(0, 0));

    const QModelIndex currentIndex = m_proxyModel->mapToSource(m_categoryList->currentIndex());
    if (!currentIndex.isValid())
646
        return;
647 648 649

    Category *category = m_model->categories().at(currentIndex.row());
    updateEnabledTabs(category, text);
con's avatar
con committed
650 651 652 653
}

void SettingsDialog::accept()
{
654 655 656
    if (m_finished)
        return;
    m_finished = true;
657
    disconnectTabWidgets();
658
    m_applied = true;
659
    foreach (IOptionsPage *page, m_visitedPages)
660
        page->apply();
661
    foreach (IOptionsPage *page, m_pages)
662
        page->finish();
con's avatar
con committed
663 664 665 666 667
    done(QDialog::Accepted);
}

void SettingsDialog::reject()
{
668 669 670
    if (m_finished)
        return;
    m_finished = true;
671
    disconnectTabWidgets();
hjk's avatar
hjk committed
672
    foreach (IOptionsPage *page, m_pages)
673
        page->finish();
con's avatar
con committed
674 675
    done(QDialog::Rejected);
}
676 677 678

void SettingsDialog::apply()
{
679
    foreach (IOptionsPage *page, m_visitedPages)
680
        page->apply();
681 682 683
    m_applied = true;
}

684 685
void SettingsDialog::done(int val)
{
hjk's avatar
hjk committed
686
    QSettings *settings = ICore::settings();
hjk's avatar
hjk committed
687
    settings->setValue(QLatin1String(pageKeyC), m_currentPage.toSetting());
688

Tobias Hunger's avatar
Tobias Hunger committed
689 690
    ICore::saveSettings(); // save all settings

691 692 693 694 695 696 697 698
    // exit all additional event loops, see comment in execDialog()
    QListIterator<QEventLoop *> it(m_eventLoops);
    it.toBack();
    while (it.hasPrevious()) {
        QEventLoop *loop = it.previous();
        loop->exit();
    }

699 700
    QDialog::done(val);
}
701

702 703 704 705 706 707 708 709
/**
 * Override to make sure the settings dialog starts up as small as possible.
 */
QSize SettingsDialog::sizeHint() const
{
    return minimumSize();
}

710
SettingsDialog *SettingsDialog::getSettingsDialog(QWidget *parent, Id initialPage)
711
{
hjk's avatar
hjk committed
712
    if (!m_instance)
713
        m_instance = new SettingsDialog(parent);
714
    m_instance->showPage(initialPage);
715 716 717 718 719 720 721
    return m_instance;
}

bool SettingsDialog::execDialog()
{
    if (!m_running) {
        m_running = true;
722
        m_finished = false;
Eike Ziller's avatar
Eike Ziller committed
723 724 725
        static const QLatin1String kPreferenceDialogSize("Core/PreferenceDialogSize");
        if (ICore::settings()->contains(kPreferenceDialogSize))
            resize(ICore::settings()->value(kPreferenceDialogSize).toSize());
726
        exec();
727 728
        m_running = false;
        m_instance = 0;
Eike Ziller's avatar
Eike Ziller committed
729
        ICore::settings()->setValue(kPreferenceDialogSize, size());
730 731 732
        // make sure that the current "single" instance is deleted
        // we can't delete right away, since we still access the m_applied member
        deleteLater();
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
    } else {
        // exec dialog is called while the instance is already running
        // this can happen when a event triggers a code path that wants to
        // show the settings dialog again
        // e.g. when starting the debugger (with non-built debugging helpers),
        // and manually opening the settings dialog, after the debugger hit
        // a break point it will complain about missing helper, and offer the
        // option to open the settings dialog.
        // Keep the UI running by creating another event loop.
        QEventLoop *loop = new QEventLoop(this);
        m_eventLoops.append(loop);
        loop->exec();
    }
    return m_applied;
}

749 750
} // namespace Internal
} // namespace Core