Preferences: Set up infrastructure for a filter widget.

Add filter widget, use a special QSortFilterProxyModel +
StandardItemModel, add a slot selecting the page.
Add matches() to IOptionsPage.
This commit is contained in:
Friedemann Kleint
2009-11-19 17:47:13 +01:00
parent d49dc172ad
commit 9a7729ebdc
7 changed files with 304 additions and 102 deletions

View File

@@ -42,5 +42,6 @@
\o trCategory() is the translated category
\o apply() is called to store the settings. It should detect if any changes have been
made and store those.
\o matches() is used for the options dialog search filter.
\endlist
*/

View File

@@ -51,6 +51,7 @@ public:
virtual QString trName() const = 0;
virtual QString category() const = 0;
virtual QString trCategory() const = 0;
virtual bool matches(const QString & /* searchKeyWord*/) const { return false; }
virtual QWidget *createPage(QWidget *parent) = 0;
virtual void apply() = 0;

View File

@@ -32,26 +32,152 @@
#include <extensionsystem/pluginmanager.h>
#include "icore.h"
#include <utils/qtcassert.h>
#include <QtCore/QSettings>
#include <QtGui/QHeaderView>
#include <QtGui/QPushButton>
#include <QtGui/QStandardItemModel>
#include <QtGui/QStandardItem>
#include <QtGui/QSortFilterProxyModel>
#include <QtGui/QItemSelectionModel>
#include <QtGui/QIcon>
namespace {
struct PageData {
int index;
QString category;
QString id;
};
enum ItemType { CategoryItem, PageItem };
enum { TypeRole = Qt::UserRole + 1,
IndexRole = Qt::UserRole + 2,
PageRole = Qt::UserRole + 3 };
Q_DECLARE_METATYPE(Core::IOptionsPage*)
static const char categoryKeyC[] = "General/LastPreferenceCategory";
static const char pageKeyC[] = "General/LastPreferencePage";
namespace Core {
namespace Internal {
// Create item on either model or parent item
template<class Parent>
inline QStandardItem *createStandardItem(Parent *parent,
const QString &text,
ItemType type = CategoryItem,
int index = -1,
IOptionsPage *page = 0)
{
QStandardItem *rc = new QStandardItem(text);
rc->setData(QVariant(int(type)), TypeRole);
rc->setData(QVariant(index), IndexRole);
rc->setData(qVariantFromValue(page), PageRole);
parent->appendRow(rc);
return rc;
}
Q_DECLARE_METATYPE(::PageData);
static inline ItemType itemTypeOfItem(const QStandardItem *item)
{
return static_cast<ItemType>(item->data(TypeRole).toInt());
}
using namespace Core;
using namespace Core::Internal;
static inline ItemType itemTypeOfItem(const QAbstractItemModel *model, const QModelIndex &index)
{
return static_cast<ItemType>(model->data(index, TypeRole).toInt());
}
static inline int indexOfItem(const QStandardItem *item)
{
return item->data(IndexRole).toInt();
}
static inline IOptionsPage *pageOfItem(const QStandardItem *item)
{
return qvariant_cast<IOptionsPage *>(item->data(PageRole));
}
static inline IOptionsPage *pageOfItem(const QAbstractItemModel *model, const QModelIndex &index)
{
return qvariant_cast<IOptionsPage *>(model->data(index, PageRole));
}
// A filter model that returns true for the parent (category) nodes
// (which by default do not match the search string and are thus collapsed)
// and additionally checks IOptionsPage::matches().
class PageFilterModel : public QSortFilterProxyModel {
public:
explicit PageFilterModel(QObject *parent = 0) : QSortFilterProxyModel(parent) {}
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
};
bool PageFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (!source_parent.isValid())
return true; // Always true for parents/categories.
// Regular contents check, then check page-filter.
if (QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent))
return true;
if (const IOptionsPage *page = pageOfItem(sourceModel(), source_parent.child(source_row, 0)))
return page->matches(filterRegExp().pattern());
return false;
}
// Populate a model with pages.
static QStandardItemModel *pageModel(const QList<IOptionsPage*> &pages,
QObject *parent,
const QString &initialCategory,
const QString &initialPageId,
QModelIndex *initialIndex)
{
QStandardItemModel *model = new QStandardItemModel(0, 1, parent);
const QChar hierarchySeparator = QLatin1Char('|');
int index = 0;
QMap<QString, QStandardItem *> categories;
foreach (IOptionsPage *page, pages) {
const QStringList categoriesId = page->category().split(hierarchySeparator);
const QStringList trCategories = page->trCategory().split(hierarchySeparator);
const int categoryDepth = categoriesId.size();
if (categoryDepth != trCategories.size()) {
qWarning("Internal error: Hierarchy mismatch in settings page %s.", qPrintable(page->id()));
continue;
}
// Retrieve/Create parent items for nested categories "Cat1|Cat2|Cat3"
QString currentCategory = categoriesId.at(0);
QStandardItem *treeItem = categories.value(currentCategory, 0);
if (!treeItem) {
treeItem = createStandardItem(model, trCategories.at(0), CategoryItem);
categories.insert(currentCategory, treeItem);
}
for (int cat = 1; cat < categoryDepth; cat++) {
const QString fullCategory = currentCategory + hierarchySeparator + categoriesId.at(cat);
treeItem = categories.value(fullCategory, 0);
if (!treeItem) {
QStandardItem *parentItem = categories.value(currentCategory);
QTC_ASSERT(parentItem, return model)
treeItem = createStandardItem(parentItem, trCategories.at(cat), CategoryItem);
categories.insert(fullCategory, treeItem);
}
currentCategory = fullCategory;
}
// Append page item
QTC_ASSERT(treeItem, return model)
QStandardItem *item = createStandardItem(treeItem, page->trName(), PageItem, index, page);
if (currentCategory == initialCategory && page->id() == initialPageId) {
*initialIndex = model->indexFromItem(item);
}
index++;
}
return model;
}
SettingsDialog::SettingsDialog(QWidget *parent, const QString &categoryId,
const QString &pageId)
: QDialog(parent), m_applied(false)
const QString &pageId) :
QDialog(parent),
m_pages(ExtensionSystem::PluginManager::instance()->getObjects<IOptionsPage>()),
m_proxyModel(new PageFilterModel),
m_model(0),
m_applied(false)
{
setupUi(this);
#ifdef Q_OS_MAC
@@ -63,72 +189,31 @@ SettingsDialog::SettingsDialog(QWidget *parent, const QString &categoryId,
QString initialPage = pageId;
if (initialCategory.isEmpty() && initialPage.isEmpty()) {
QSettings *settings = ICore::instance()->settings();
initialCategory = settings->value("General/LastPreferenceCategory", QVariant(QString())).toString();
initialPage = settings->value("General/LastPreferencePage", QVariant(QString())).toString();
initialCategory = settings->value(QLatin1String(categoryKeyC), QVariant(QString())).toString();
initialPage = settings->value(QLatin1String(pageKeyC), QVariant(QString())).toString();
}
buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply()));
foreach(IOptionsPage *page, m_pages)
stackedPages->addWidget(page->createPage(0));
splitter->setCollapsible(1, false);
pageTree->header()->setVisible(false);
connect(pageTree, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)),
this, SLOT(pageSelected()));
QMap<QString, QTreeWidgetItem *> categories;
QList<IOptionsPage*> pages =
ExtensionSystem::PluginManager::instance()->getObjects<IOptionsPage>();
int index = 0;
foreach (IOptionsPage *page, pages) {
PageData pageData;
pageData.index = index;
pageData.category = page->category();
pageData.id = page->id();
QTreeWidgetItem *item = new QTreeWidgetItem;
item->setText(0, page->trName());
item->setData(0, Qt::UserRole, qVariantFromValue(pageData));
QStringList categoriesId = page->category().split(QLatin1Char('|'));
QStringList trCategories = page->trCategory().split(QLatin1Char('|'));
QString currentCategory = categoriesId.at(0);
QTreeWidgetItem *treeitem;
if (!categories.contains(currentCategory)) {
treeitem = new QTreeWidgetItem(pageTree);
treeitem->setText(0, trCategories.at(0));
treeitem->setData(0, Qt::UserRole, qVariantFromValue(pageData));
categories.insert(currentCategory, treeitem);
}
int catCount = 1;
while (catCount < categoriesId.count()) {
if (!categories.contains(currentCategory + QLatin1Char('|') + categoriesId.at(catCount))) {
treeitem = new QTreeWidgetItem(categories.value(currentCategory));
currentCategory += QLatin1Char('|') + categoriesId.at(catCount);
treeitem->setText(0, trCategories.at(catCount));
treeitem->setData(0, Qt::UserRole, qVariantFromValue(pageData));
categories.insert(currentCategory, treeitem);
} else {
currentCategory += QLatin1Char('|') + categoriesId.at(catCount);
}
++catCount;
}
categories.value(currentCategory)->addChild(item);
m_pages.append(page);
stackedPages->addWidget(page->createPage(stackedPages));
if (page->id() == initialPage && currentCategory == initialCategory) {
stackedPages->setCurrentIndex(stackedPages->count());
pageTree->setCurrentItem(item);
}
index++;
QModelIndex initialIndex;
m_model = pageModel(m_pages, 0, initialCategory, initialPage, &initialIndex);
m_proxyModel->setFilterKeyColumn(0);
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxyModel->setSourceModel(m_model);
pageTree->setModel(m_proxyModel);
pageTree->setSelectionMode(QAbstractItemView::SingleSelection);
connect(pageTree->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
this, SLOT(currentChanged(QModelIndex,QModelIndex)));
if (initialIndex.isValid()) {
const QModelIndex proxyIndex = m_proxyModel->mapFromSource(initialIndex);
pageTree->selectionModel()->setCurrentIndex(proxyIndex, QItemSelectionModel::ClearAndSelect);
}
QList<int> sizes;
@@ -137,20 +222,92 @@ SettingsDialog::SettingsDialog(QWidget *parent, const QString &categoryId,
splitter->setStretchFactor(splitter->indexOf(pageTree), 0);
splitter->setStretchFactor(splitter->indexOf(layoutWidget), 1);
filterClearButton->setIcon(QIcon(QLatin1String(":/debugger/images/delete.png")));
connect(filterClearButton, SIGNAL(clicked()), filterLineEdit, SLOT(clear()));
// The order of the slot connection matters here, the filter slot
// opens the matching page after the model has filtered.
connect(filterLineEdit, SIGNAL(textChanged(QString)),
m_proxyModel, SLOT(setFilterFixedString(QString)));
connect(filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filter(QString)));
}
SettingsDialog::~SettingsDialog()
{
}
void SettingsDialog::pageSelected()
void SettingsDialog::showPage(const QStandardItem *item)
{
QTreeWidgetItem *item = pageTree->currentItem();
PageData data = item->data(0, Qt::UserRole).value<PageData>();
int index = data.index;
m_currentCategory = data.category;
m_currentPage = data.id;
stackedPages->setCurrentIndex(index);
// Show a page item or recurse to the first page of a category
// if a category was hit.
switch (itemTypeOfItem(item)) {
case PageItem: {
const IOptionsPage *page = pageOfItem(item);
m_currentCategory = page->category();
m_currentPage = page->id();
stackedPages->setCurrentIndex(indexOfItem(item));
}
break;
case CategoryItem:
if (const QStandardItem *child = item->child(0, 0))
showPage(child);
break;
}
}
void SettingsDialog::currentChanged(const QModelIndex & current, const QModelIndex & /*previous */)
{
if (current.isValid())
if (const QStandardItem *item = m_model->itemFromIndex(m_proxyModel->mapToSource(current)))
showPage(item);
}
// Helpers that recurse down the model to find a page.
static QModelIndex findPage(const QModelIndex &root)
{
QTC_ASSERT(root.isValid(), return root)
const QAbstractItemModel *model = root.model();
// Found a page!
if (itemTypeOfItem(model, root) == PageItem)
return root;
// Recurse down category.
const int childCount = model->rowCount(root);
for (int c = 0; c < childCount; c++) {
const QModelIndex page = findPage(root.child(c, 0));
if (page.isValid())
return page;
}
return QModelIndex();
}
static QModelIndex findPage(const QAbstractItemModel *model)
{
const QModelIndex invalid;
// Traverse top categories
const int rootItemCount = model->rowCount(invalid);
for (int c = 0; c < rootItemCount; c++) {
const QModelIndex page = findPage(model->index(c, 0, invalid));
if (page.isValid())
return page;
}
return invalid;
}
void SettingsDialog::filter(const QString &text)
{
// Filter cleared, collapse all.
if (text.isEmpty()) {
pageTree->collapseAll();
return;
}
// Expand match and select the first page. Note: This depends
// on the order of slot invocation, needs to be done after filtering
if (!m_proxyModel->rowCount(QModelIndex()))
return;
pageTree->expandAll();
const QModelIndex firstVisiblePage = findPage(m_proxyModel);
if (firstVisiblePage.isValid())
pageTree->selectionModel()->setCurrentIndex(firstVisiblePage, QItemSelectionModel::ClearAndSelect);
}
void SettingsDialog::accept()
@@ -187,7 +344,10 @@ bool SettingsDialog::execDialog()
void SettingsDialog::done(int val)
{
QSettings *settings = ICore::instance()->settings();
settings->setValue("General/LastPreferenceCategory", m_currentCategory);
settings->setValue("General/LastPreferencePage", m_currentPage);
settings->setValue(QLatin1String(categoryKeyC), m_currentCategory);
settings->setValue(QLatin1String(pageKeyC), m_currentPage);
QDialog::done(val);
}
}
}

View File

@@ -36,6 +36,13 @@
#include "coreplugin/dialogs/ioptionspage.h"
QT_BEGIN_NAMESPACE
class QModelIndex;
class QStandardItemModel;
class QStandardItem;
class QSortFilterProxyModel;
QT_END_NAMESPACE
namespace Core {
namespace Internal {
@@ -57,13 +64,18 @@ public slots:
void done(int);
private slots:
void pageSelected();
void accept();
void reject();
void apply();
void currentChanged(const QModelIndex &current, const QModelIndex &previous);
void filter(const QString &text);
private:
QList<Core::IOptionsPage*> m_pages;
void showPage(const QStandardItem *item);
const QList<Core::IOptionsPage*> m_pages;
QSortFilterProxyModel *m_proxyModel;
QStandardItemModel *m_model;
bool m_applied;
QString m_currentCategory;
QString m_currentPage;

View File

@@ -13,33 +13,39 @@
<property name="windowTitle">
<string>Options</string>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="margin">
<number>9</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QTreeWidget" name="pageTree">
<widget class="QWidget" name="">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="filterLayout">
<item>
<widget class="QLineEdit" name="filterLineEdit"/>
</item>
<item>
<widget class="QToolButton" name="filterClearButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="pageTree">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="columnCount">
<number>1</number>
</property>
<column>
<property name="text">
<string>0</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout">

View File

@@ -80,6 +80,17 @@ void VCSBaseSettingsWidget::setSettings(const VCSBaseSettings &s)
m_ui->lineWrapSpinBox->setValue(s.lineWrapWidth);
}
QString VCSBaseSettingsWidget::searchKeyWordMatchString() const
{
const QChar blank = QLatin1Char(' ');
QString rc = m_ui->submitMessageCheckScriptLabel->text();
rc += blank;
rc += m_ui->nickNameMailMapLabel->text();
rc += blank;
rc += m_ui->nickNameFieldsFileLabel->text();
return rc;
}
// --------------- VCSBaseSettingsPage
VCSBaseSettingsPage::VCSBaseSettingsPage(QObject *parent) :
Core::IOptionsPage(parent)
@@ -119,6 +130,7 @@ QWidget *VCSBaseSettingsPage::createPage(QWidget *parent)
{
m_widget = new VCSBaseSettingsWidget(parent);
m_widget->setSettings(m_settings);
m_searchKeyWords = m_widget->searchKeyWordMatchString();
return m_widget;
}
@@ -134,5 +146,10 @@ void VCSBaseSettingsPage::apply()
}
}
bool VCSBaseSettingsPage::matches(const QString &key) const
{
return m_searchKeyWords.contains(key, Qt::CaseInsensitive);
}
}
}

View File

@@ -53,6 +53,8 @@ public:
VCSBaseSettings settings() const;
void setSettings(const VCSBaseSettings &s);
QString searchKeyWordMatchString() const;
private:
Ui::VCSBaseSettingsPage *m_ui;
};
@@ -72,6 +74,7 @@ public:
virtual QWidget *createPage(QWidget *parent);
virtual void apply();
virtual void finish() { }
virtual bool matches(const QString &key) const;
VCSBaseSettings settings() const { return m_settings; }
@@ -80,8 +83,10 @@ signals:
private:
void updateNickNames();
VCSBaseSettingsWidget* m_widget;
VCSBaseSettings m_settings;
QString m_searchKeyWords;
};
} // namespace Internal