forked from qt-creator/qt-creator
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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user