* custom keyboard search/filter which shows only those layouts whose

names include the entered character sequence in the same order. 


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@23426 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Stefan Schimanski 2008-03-04 09:46:35 +00:00
parent a90096d4d5
commit 3931ff3f2e
2 changed files with 229 additions and 49 deletions

View File

@ -43,10 +43,16 @@
#include "support/lyxalgo.h" // sorted #include "support/lyxalgo.h" // sorted
#include <QComboBox> #include <QComboBox>
#include <QHeaderView>
#include <QKeyEvent>
#include <QList>
#include <QPixmap>
#include <QSortFilterProxyModel>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QToolBar> #include <QToolBar>
#include <QToolButton> #include <QToolButton>
#include <QAction> #include <QVariant>
#include <QPixmap>
#include <boost/assert.hpp> #include <boost/assert.hpp>
@ -230,6 +236,64 @@ static QIcon getIcon(FuncRequest const & f, bool unknown)
// //
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
class GuiFilterProxyModel : public QSortFilterProxyModel
{
public:
///
GuiFilterProxyModel(QObject * parent)
: QSortFilterProxyModel(parent) {}
///
QVariant data(const QModelIndex & index, int role) const
{
GuiLayoutBox * p = static_cast<GuiLayoutBox *>(parent());
QString const & f = p->filter();
if (!f.isEmpty() && index.isValid() && role == Qt::DisplayRole) {
// step through data item and put "(x)" for every matching character
QString s = QSortFilterProxyModel::data(index, role).toString();
QString r;
int lastp = -1;
p->filter();
for (int i = 0; i < f.length(); ++i) {
int p = s.indexOf(f[i], lastp + 1, Qt::CaseInsensitive);
BOOST_ASSERT(p != -1);
if (lastp == p - 1 && lastp != -1) {
// remove ")" and append "x)"
r = r.left(r.length() - 1) + s[p] + ")";
} else {
// append "(x)"
r += s.mid(lastp + 1, p - lastp - 1);
r += "(" + s[p] + ")";
}
lastp = p;
}
r += s.mid(lastp + 1);
return r;
}
return QSortFilterProxyModel::data(index, role);
}
///
void setCharFilter(QString const & f)
{
setFilterRegExp(charFilterRegExp(f));
reset();
}
private:
///
QString charFilterRegExp(QString const & filter)
{
QString re;
for (int i = 0; i < filter.length(); ++i)
re += ".*" + QRegExp::escape(filter[i]);
return re;
}
};
GuiLayoutBox::GuiLayoutBox(GuiView & owner) GuiLayoutBox::GuiLayoutBox(GuiView & owner)
: owner_(owner) : owner_(owner)
{ {
@ -238,81 +302,169 @@ GuiLayoutBox::GuiLayoutBox(GuiView & owner)
setMinimumWidth(sizeHint().width()); setMinimumWidth(sizeHint().width());
setMaxVisibleItems(100); setMaxVisibleItems(100);
QObject::connect(this, SIGNAL(activated(QString)), // set the layout model with two columns
this, SLOT(selected(QString))); // 1st: translated layout names
// 2nd: raw layout names
model_ = new QStandardItemModel(0, 2, this);
filterModel_ = new GuiFilterProxyModel(this);
filterModel_->setSourceModel(model_);
filterModel_->setDynamicSortFilter(true);
filterModel_->setFilterCaseSensitivity(Qt::CaseInsensitive);
setModel(filterModel_);
// for the filtering we have to intercept characters
view()->installEventFilter(this);
QObject::connect(this, SIGNAL(activated(int)),
this, SLOT(selected(int)));
owner_.setLayoutDialog(this); owner_.setLayoutDialog(this);
updateContents(true); updateContents(true);
} }
void GuiLayoutBox::setFilter(QString const & s)
{
// remember old selection
int sel = currentIndex();
if (sel != -1)
lastSel_ = filterModel_->mapToSource(filterModel_->index(sel, 0)).row();
filter_ = s;
filterModel_->setCharFilter(s);
// restore old selection
if (lastSel_ != -1) {
QModelIndex i = filterModel_->mapFromSource(model_->index(lastSel_, 0));
if (i.isValid())
setCurrentIndex(i.row());
}
}
void GuiLayoutBox::resetFilter()
{
setFilter(QString());
}
bool GuiLayoutBox::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::KeyPress) {
QKeyEvent * ke = static_cast<QKeyEvent*>(e);
bool modified = (ke->modifiers() == Qt::ControlModifier)
|| (ke->modifiers() == Qt::AltModifier)
|| (ke->modifiers() == Qt::MetaModifier);
switch (ke->key()) {
case Qt::Key_Escape:
if (!modified && !filter_.isEmpty()) {
resetFilter();
return true;
}
break;
case Qt::Key_Backspace:
if (!modified) {
// cut off one character
setFilter(filter_.left(filter_.length() - 1));
}
break;
default:
if (modified || ke->text().isEmpty())
break;
// find chars for the filter string
QString s;
for (int i = 0; i < ke->text().length(); ++i) {
QChar c = ke->text()[i];
if (c.isLetterOrNumber()
|| c.isSymbol()
|| c.isPunct()
|| c.category() == QChar::Separator_Space) {
s += c;
}
}
if (!s.isEmpty()) {
// append new chars to the filter string
setFilter(filter_ + s);
return true;
}
break;
}
}
return QComboBox::eventFilter(o, e);
}
void GuiLayoutBox::set(docstring const & layout) void GuiLayoutBox::set(docstring const & layout)
{ {
resetFilter();
if (!text_class_) if (!text_class_)
return; return;
QString const & name = toqstr(translateIfPossible( QString const & name = toqstr((*text_class_)[layout]->name());
(*text_class_)[layout]->name()));
if (name == currentText()) if (name == currentText())
return; return;
int i = findText(name); QList<QStandardItem *> r = model_->findItems(name, Qt::MatchExactly, 1);
if (i == -1) { if (r.empty()) {
lyxerr << "Trying to select non existent layout type " lyxerr << "Trying to select non existent layout type "
<< fromqstr(name) << endl; << fromqstr(name) << endl;
return; return;
} }
setCurrentIndex(i); setCurrentIndex(filterModel_->mapFromSource(r.first()->index()).row());
} }
void GuiLayoutBox::addItemSort(QString const & item, bool sorted) void GuiLayoutBox::addItemSort(docstring const & item, bool sorted)
{ {
//FIXME QString qitem = toqstr(item);
//Since we are only storing the text used for display, we have no choice QList<QStandardItem *> row;
//below but to compare translated strings to figure out which layout the row.append(new QStandardItem(toqstr(translateIfPossible(item))));
//user wants. This is not ideal. A better way is the way module names are row.append(new QStandardItem(qitem));
//handled in GuiDocument: viz, the untranslated name can be associated
//with the item by using GuiIdListModel. // the simple unsorted case
int const end = count(); int const end = model_->rowCount();
if (!sorted || end < 2 || item[0].category() != QChar::Letter_Uppercase) { if (!sorted || end < 2 || qitem[0].category() != QChar::Letter_Uppercase) {
addItem(item); model_->appendRow(row);
return; return;
} }
// Let the default one be at the beginning // find row to insert the item
int i = 1; int i = 1; // skip the Standard layout
for (setCurrentIndex(i); currentText().localeAwareCompare(item) < 0; ) { QString is = model_->item(i, 1)->text();
while (is.compare(qitem) < 0) {
// e.g. --Separator-- // e.g. --Separator--
if (currentText()[0].category() != QChar::Letter_Uppercase) if (is[0].category() != QChar::Letter_Uppercase)
break; break;
if (++i == end) ++i;
if (i == end)
break; break;
setCurrentIndex(i); QString is = model_->item(i, 1)->text();
} }
insertItem(i, item); model_->insertRow(i, row);
} }
void GuiLayoutBox::updateContents(bool reset) void GuiLayoutBox::updateContents(bool reset)
{ {
resetFilter();
Buffer const * buffer = owner_.buffer(); Buffer const * buffer = owner_.buffer();
if (!buffer) { if (!buffer) {
clear(); model_->clear();
setEnabled(false); setEnabled(false);
text_class_ = 0; text_class_ = 0;
inset_ = 0; inset_ = 0;
return; return;
} }
DocumentClass const * text_class = &buffer->params().documentClass();
Inset const * inset =
owner_.view()->cursor().innerParagraph().inInset();
// we'll only update the layout list if the text class has changed // we'll only update the layout list if the text class has changed
// or we've moved from one inset to another // or we've moved from one inset to another
DocumentClass const * text_class = &buffer->params().documentClass();
Inset const * inset =
owner_.view()->cursor().innerParagraph().inInset();
if (!reset && text_class_ == text_class && inset_ == inset) { if (!reset && text_class_ == text_class && inset_ == inset) {
set(owner_.view()->cursor().innerParagraph().layout()->name()); set(owner_.view()->cursor().innerParagraph().layout()->name());
return; return;
@ -321,8 +473,7 @@ void GuiLayoutBox::updateContents(bool reset)
inset_ = inset; inset_ = inset;
text_class_ = text_class; text_class_ = text_class;
clear(); model_->clear();
for (size_t i = 0; i != text_class_->layoutCount(); ++i) { for (size_t i = 0; i != text_class_->layoutCount(); ++i) {
Layout const & lt = *text_class_->layout(i); Layout const & lt = *text_class_->layout(i);
docstring const & name = lt.name(); docstring const & name = lt.name();
@ -335,7 +486,7 @@ void GuiLayoutBox::updateContents(bool reset)
if (name == text_class_->emptyLayoutName() && inset && if (name == text_class_->emptyLayoutName() && inset &&
!inset->forceEmptyLayout() && !inset->useEmptyLayout()) !inset->forceEmptyLayout() && !inset->useEmptyLayout())
continue; continue;
addItemSort(toqstr(translateIfPossible(name)), lyxrc.sort_layouts); addItemSort(name, lyxrc.sort_layouts);
} }
set(owner_.view()->cursor().innerParagraph().layout()->name()); set(owner_.view()->cursor().innerParagraph().layout()->name());
@ -343,32 +494,35 @@ void GuiLayoutBox::updateContents(bool reset)
// needed to recalculate size hint // needed to recalculate size hint
hide(); hide();
setMinimumWidth(sizeHint().width()); setMinimumWidth(sizeHint().width());
setEnabled(!buffer->isReadonly()); setEnabled(!buffer->isReadonly());
show(); show();
} }
void GuiLayoutBox::selected(const QString & str) void GuiLayoutBox::selected(int index)
{ {
owner_.setFocus(); // get selection
updateContents(false); QModelIndex mindex = filterModel_->mapToSource(filterModel_->index(index, 1));
if (!text_class_) docstring const name = qstring_to_ucs4(model_->itemFromIndex(mindex)->text());
return;
docstring const name = qstring_to_ucs4(str); owner_.setFocus();
if (!text_class_) {
updateContents(false);
resetFilter();
return;
}
// find corresponding text class
for (size_t i = 0; i != text_class_->layoutCount(); ++i) { for (size_t i = 0; i != text_class_->layoutCount(); ++i) {
docstring const & itname = text_class_->layout(i)->name(); docstring const & itname = text_class_->layout(i)->name();
// FIXME: Comparing translated strings is not ideal. if (itname == name) {
// This should be done the way module names are handled
// in GuiDocument: viz, the untranslated name should be
// associated with the item via QComboBox::setItemData().
if (translateIfPossible(itname) == name) {
FuncRequest const func(LFUN_LAYOUT, itname, FuncRequest const func(LFUN_LAYOUT, itname,
FuncRequest::TOOLBAR); FuncRequest::TOOLBAR);
theLyXFunc().setLyXView(&owner_); theLyXFunc().setLyXView(&owner_);
lyx::dispatch(func); lyx::dispatch(func);
updateContents(false); updateContents(false);
resetFilter();
return; return;
} }
} }

View File

@ -22,6 +22,8 @@
#include <QToolBar> #include <QToolBar>
#include <QComboBox> #include <QComboBox>
class QStandardItemModel;
namespace lyx { namespace lyx {
class Inset; class Inset;
@ -31,6 +33,7 @@ class ToolbarItem;
namespace frontend { namespace frontend {
class GuiCommandBuffer; class GuiCommandBuffer;
class GuiFilterProxyModel;
class GuiView; class GuiView;
class Action; class Action;
@ -46,15 +49,38 @@ public:
/// Populate the layout combobox. /// Populate the layout combobox.
void updateContents(bool reset); void updateContents(bool reset);
/// Add Item to Layout box according to sorting settings from preferences /// Add Item to Layout box according to sorting settings from preferences
void addItemSort(QString const & item, bool sorted); void addItemSort(docstring const & item, bool sorted);
///
bool eventFilter(QObject *o, QEvent *e);
///
QString const & filter() { return filter_; }
private Q_SLOTS: private Q_SLOTS:
void selected(const QString & str); ///
void selected(int index);
private: private:
///
void resetFilter();
///
void setFilter(QString const & s);
///
GuiView & owner_; GuiView & owner_;
///
DocumentClass const * text_class_; DocumentClass const * text_class_;
///
Inset const * inset_; Inset const * inset_;
/// the layout model: 1st column translated, 2nd column raw layout name
QStandardItemModel * model_;
/// the proxy model filtering \c model_
GuiFilterProxyModel * filterModel_;
/// the (model-) index of the last successful selection
int lastSel_;
/// the character filter
QString filter_;
}; };