settingsdialog.cpp 24.2 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

hjk's avatar
hjk committed
30
#include <extensionsystem/pluginmanager.h>
31
#include <utils/algorithm.h>
32
#include <utils/hostosinfo.h>
33
#include <utils/fancylineedit.h>
34
#include <utils/qtcassert.h>
35

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

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

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

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

63
64
65
66
67
68
69
70
71
72
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()
{
    QList<IOptionsPage*> rc = ExtensionSystem::PluginManager::getObjects<IOptionsPage>();
73
    std::stable_sort(rc.begin(), rc.end(), optionsPageLessThan);
74
75
76
    return rc;
}

77
78
// ----------- Category model

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

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

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

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

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

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

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

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

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

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

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

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

    return QVariant();
154
155
}

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

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

    // Put the pages in categories
    foreach (IOptionsPage *page, pages) {
168
169
170
        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
171
        const Id categoryId = page->category();
172
173
174
175
        Category *category = findCategoryById(categoryId);
        if (!category) {
            category = new Category;
            category->id = categoryId;
176
177
178
179
180
            category->tabWidget = 0;
            category->index = -1;
            m_categories.append(category);
        }
        if (category->displayName.isEmpty())
181
            category->displayName = page->displayCategory();
182
        if (category->icon.isNull())
183
            category->icon = page->categoryIcon();
184
185
186
187
        category->pages.append(page);
    }

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

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

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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;
225
        std::stable_sort(category->pages.begin(), category->pages.end(), optionsPageLessThan);
226
227
228
    }
}

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

    return 0;
238
}
239

240
241
242
243
244
245
246
247
// ----------- 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
{
248
public:
249
250
251
252
    explicit CategoryFilterModel(QObject *parent = 0)
        : QSortFilterProxyModel(parent)
    {}

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

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

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

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

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

283
// ----------- Category list view
284

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

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;
    }
};

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

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

    // 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);
    }
328
329
};

330
331
332
333
334
335
336
337
338
339
// ----------- 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
340
        setWidgetResizable(true);
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
391
    }
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();
    }
};


392
393
// ----------- SettingsDialog

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

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

417
    m_model->setPages(m_pages,
418
        ExtensionSystem::PluginManager::getObjects<IOptionsPageProvider>());
con's avatar
con committed
419

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

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

430
431
    // 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
432
433
434
435
    connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
            m_proxyModel, &QSortFilterProxyModel::setFilterFixedString);
    connect(m_filterLineEdit, &Utils::FancyLineEdit::filterChanged,
            this, &SettingsDialog::filter);
436
437
438
    m_categoryList->setFocus();
}

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

    int initialCategoryIndex = -1;
    int initialPageIndex = -1;
450

451
    const QList<Category*> &categories = m_model->categories();
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
    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;
                    }
474
                }
475
476
477
            }
        }
    }
478

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

482
    if (initialCategoryIndex != -1) {
483
484
485
486
487
        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));
        }
488
        m_categoryList->setCurrentIndex(modelIndex);
489
490
491
492
        if (initialPageIndex != -1) {
            if (QTC_GUARD(categories.at(initialCategoryIndex)->tabWidget))
                categories.at(initialCategoryIndex)->tabWidget->setCurrentIndex(initialPageIndex);
        }
con's avatar
con committed
493
    }
Friedemann Kleint's avatar
Friedemann Kleint committed
494
495
496
497
498
499
500
501
502
503
504
505
506
507
}

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;
508
    const int leftMargin = qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
Friedemann Kleint's avatar
Friedemann Kleint committed
509
510
511
512
    headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
    headerHLayout->addWidget(m_headerLabel);

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

515
516
517
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok |
                                                       QDialogButtonBox::Apply |
                                                       QDialogButtonBox::Cancel);
hjk's avatar
hjk committed
518
519
520
521
522
    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
523
524
525
526

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

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

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

SettingsDialog::~SettingsDialog()
{
}

542
void SettingsDialog::showCategory(int index)
con's avatar
con committed
543
{
544
    Category *category = m_model->categories().at(index);
545
    ensureCategoryWidget(category);
546
547
548
549
550
551
552
    // 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);
553
    }
554
555
556
557
558

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

    updateEnabledTabs(category, m_filterLineEdit->text());
559
560
}

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

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

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

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

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

593
void SettingsDialog::updateEnabledTabs(Category *category, const QString &searchText)
594
{
595
    int firstEnabledTab = -1;
596
597
598
    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
599
                             || page->category().toString().contains(searchText, Qt::CaseInsensitive)
600
601
602
                             || page->displayName().contains(searchText, Qt::CaseInsensitive)
                             || page->matches(searchText);
        category->tabWidget->setTabEnabled(i, enabled);
603
604
605
606
607
608
609
        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);
610
    }
611
612
}

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

623
void SettingsDialog::currentTabChanged(int index)
624
{
625
626
627
628
629
630
631
632
633
634
635
636
    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);
637
638
639
640
}

void SettingsDialog::filter(const QString &text)
{
641
642
643
644
645
646
    // 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())
647
        return;
648
649
650

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

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

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

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

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

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

692
693
694
695
696
697
698
699
    // 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();
    }

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

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

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

bool SettingsDialog::execDialog()
{
    if (!m_running) {
        m_running = true;
723
        m_finished = false;
Eike Ziller's avatar
Eike Ziller committed
724
725
726
        static const QLatin1String kPreferenceDialogSize("Core/PreferenceDialogSize");
        if (ICore::settings()->contains(kPreferenceDialogSize))
            resize(ICore::settings()->value(kPreferenceDialogSize).toSize());
727
        exec();
728
729
        m_running = false;
        m_instance = 0;
Eike Ziller's avatar
Eike Ziller committed
730
        ICore::settings()->setValue(kPreferenceDialogSize, size());
731
732
733
        // make sure that the current "single" instance is deleted
        // we can't delete right away, since we still access the m_applied member
        deleteLater();
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
    } 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;
}

750
751
} // namespace Internal
} // namespace Core