mirror of
https://git.lyx.org/repos/lyx.git
synced 2025-01-11 11:08:41 +00:00
* 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:
parent
a90096d4d5
commit
3931ff3f2e
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user