Commit 9a7729eb authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

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.
parent d49dc172
......@@ -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
*/
......@@ -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;
......
......@@ -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>
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 {
namespace {
struct PageData {
int index;
QString category;
QString id;
};
// 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());
}
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));
}
using namespace Core;
using namespace Core::Internal;
// 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);
}
}
}
......@@ -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;
......
......@@ -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">
<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 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>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<layout class="QVBoxLayout">
......
......@@ -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);
}
}
}
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment