X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiToolbar.cpp;h=f5c68b4c268d3c7824e4ef3bce64f48453f19af8;hb=2280e16db4ce39bec4ec206bfbe684337d8d31f3;hp=ecba34a10d223bec8ba6f07ed085fbb63be15eb4;hpb=7c392af6eab5e06a4836146859cbd9f2c3764420;p=lyx.git diff --git a/src/frontends/qt4/GuiToolbar.cpp b/src/frontends/qt4/GuiToolbar.cpp index ecba34a10d..f5c68b4c26 100644 --- a/src/frontends/qt4/GuiToolbar.cpp +++ b/src/frontends/qt4/GuiToolbar.cpp @@ -14,39 +14,50 @@ #include +#include "GuiView.h" +#include "GuiCommandBuffer.h" +#include "GuiToolbar.h" +#include "LyXAction.h" +#include "Action.h" +#include "qt_helpers.h" +#include "InsertTableWidget.h" + #include "Buffer.h" #include "BufferParams.h" #include "BufferView.h" #include "Cursor.h" -#include "support/debug.h" #include "FuncRequest.h" #include "FuncStatus.h" -#include "support/gettext.h" #include "IconPalette.h" #include "Layout.h" #include "LyXFunc.h" +#include "LyXRC.h" #include "Paragraph.h" #include "TextClass.h" #include "ToolbarBackend.h" -#include "GuiView.h" -#include "GuiCommandBuffer.h" -#include "GuiToolbar.h" -#include "LyXAction.h" -#include "Action.h" -#include "qt_helpers.h" -#include "InsertTableWidget.h" -#include "LyXRC.h" - +#include "support/debug.h" #include "support/filetools.h" +#include "support/gettext.h" #include "support/lstrings.h" #include "support/lyxalgo.h" // sorted +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include #include @@ -76,7 +87,7 @@ struct PngMap { bool operator<(PngMap const & lhs, PngMap const & rhs) { - return compare(lhs.key, rhs.key) < 0; + return strcmp(lhs.key, rhs.key) < 0; } @@ -122,7 +133,7 @@ PngMap sorted_png_map[] = { { "nVDash", "nvdash3" }, { "nvDash", "nvdash2" }, { "textrm \\AA", "textrm_AA"}, - { "textrm \\O", "textrm_Oe"}, + { "textrm \\O", "textrm_O"}, { "vDash", "vdash2" } }; @@ -230,124 +241,372 @@ static QIcon getIcon(FuncRequest const & f, bool unknown) // ///////////////////////////////////////////////////////////////////// +class FilterItemDelegate : public QAbstractItemDelegate { +public: + /// + explicit FilterItemDelegate(QObject * parent = 0) + : QAbstractItemDelegate(parent) {} + + /// + void paint(QPainter * painter, const QStyleOptionViewItem & option, + const QModelIndex &index) const { + QComboBox * combo = static_cast(parent()); + + // Draw using the menu item style (this is how QComboBox does it). + // But for the rich text drawing below we will call it with an + // empty string, and later then draw over it the real string. + painter->save(); + QStyleOptionMenuItem opt = getStyleOption(option, index); + QString text = underlineFilter(opt.text); + opt.text = QString(); + painter->eraseRect(option.rect); + combo->style()->drawControl(QStyle::CE_MenuItem, &opt, painter, combo->view()); + painter->restore(); + + // Draw the rich text. + painter->save(); + QColor col = opt.palette.text().color(); + if (opt.state & QStyle::State_Selected) + col = opt.palette.highlightedText().color(); + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor(QPalette::Text, col); + + QTextDocument doc; + doc.setDefaultFont(opt.font); + doc.setHtml(text); + painter->translate(opt.rect.x() + 20, opt.rect.y()); + doc.documentLayout()->draw(painter, context); + painter->restore(); + } + + /// + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const { + QComboBox * combo = static_cast(parent()); + + QStyleOptionMenuItem opt = getStyleOption(option, index); + return combo->style()->sizeFromContents( + QStyle::CT_MenuItem, &opt, option.rect.size(), combo); + } + +private: + /// + QString underlineFilter(QString const & s) const + { + // get filter + GuiLayoutBox * p = static_cast(parent()); + QString const & f = p->filter(); + if (f.isEmpty()) + return s; + + // step through data item and put "(x)" for every matching character + 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() - 4) + s[p] + ""; + } else { + // append "(x)" + r += s.mid(lastp + 1, p - lastp - 1); + r += QString("") + s[p] + ""; + } + lastp = p; + } + r += s.mid(lastp + 1); + return r; + } + + /// + QStyleOptionMenuItem getStyleOption(const QStyleOptionViewItem &option, + const QModelIndex &index) const + { + QComboBox * combo = static_cast(parent()); + + // create the options for a menu item + QStyleOptionMenuItem menuOption; + menuOption.palette = QApplication::palette("QMenu"); + menuOption.state = QStyle::State_Active | QStyle::State_Enabled; + if (option.state & QStyle::State_Selected) + menuOption.state |= QStyle::State_Selected; + menuOption.checkType = QStyleOptionMenuItem::NonExclusive; + menuOption.checked = combo->currentIndex() == index.row(); + menuOption.menuItemType = QStyleOptionMenuItem::Normal; + menuOption.text = index.model()->data(index, Qt::DisplayRole).toString() + .replace(QLatin1Char('&'), QLatin1String("&&")); + menuOption.tabWidth = 0; + menuOption.menuRect = option.rect; + menuOption.rect = option.rect; + menuOption.font = combo->font(); + menuOption.fontMetrics = QFontMetrics(menuOption.font); + return menuOption; + } +}; + + +class GuiFilterProxyModel : public QSortFilterProxyModel +{ +public: + /// + GuiFilterProxyModel(QObject * parent) + : QSortFilterProxyModel(parent) {} + + /// + void setCharFilter(QString const & f) + { + setFilterRegExp(charFilterRegExp(f)); + dataChanged(index(0, 0), index(rowCount() - 1, 1)); + } + +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) - : owner_(owner) + : owner_(owner), filterItemDelegate_(new FilterItemDelegate(this)) { setSizeAdjustPolicy(QComboBox::AdjustToContents); setFocusPolicy(Qt::ClickFocus); setMinimumWidth(sizeHint().width()); setMaxVisibleItems(100); - QObject::connect(this, SIGNAL(activated(QString)), - this, SLOT(selected(QString))); + // set the layout model with two columns + // 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); + view()->setItemDelegateForColumn(0, filterItemDelegate_); + + QObject::connect(this, SIGNAL(activated(int)), + this, SLOT(selected(int))); owner_.setLayoutDialog(this); 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) + return QComboBox::eventFilter(o, e); + + QKeyEvent * ke = static_cast(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) { + resetFilter(); + if (!text_class_) return; - QString const & name = toqstr(translateIfPossible( - (*text_class_)[layout]->name())); - + QString const & name = toqstr((*text_class_)[layout]->name()); if (name == currentText()) return; - int i = findText(name); - if (i == -1) { + QList r = model_->findItems(name, Qt::MatchExactly, 1); + if (r.empty()) { lyxerr << "Trying to select non existent layout type " << fromqstr(name) << endl; 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) { - int const end = count(); - if (!sorted || end < 2 || item[0].category() != QChar::Letter_Uppercase) { - addItem(item); + QString qitem = toqstr(item); + QString titem = toqstr(translateIfPossible(item)); + + QList row; + row.append(new QStandardItem(titem)); + row.append(new QStandardItem(qitem)); + + // the simple unsorted case + int const end = model_->rowCount(); + if (!sorted || end < 2 || qitem[0].category() != QChar::Letter_Uppercase) { + model_->appendRow(row); return; } - // Let the default one be at the beginning - int i = 1; - for (setCurrentIndex(i); currentText() < item;) { + // find row to insert the item + int i = 1; // skip the Standard layout + QString is = model_->item(i, 0)->text(); + while (is.compare(titem) < 0) { // e.g. --Separator-- - if (currentText()[0].category() != QChar::Letter_Uppercase) + if (is[0].category() != QChar::Letter_Uppercase) break; - if (++i == end) + ++i; + if (i == end) break; - setCurrentIndex(i); + is = model_->item(i, 0)->text(); } - insertItem(i, item); + model_->insertRow(i, row); } void GuiLayoutBox::updateContents(bool reset) { + resetFilter(); + Buffer const * buffer = owner_.buffer(); if (!buffer) { - clear(); + model_->clear(); setEnabled(false); text_class_ = 0; + inset_ = 0; return; } - setEnabled(true); - TextClass const * text_class = &buffer->params().getTextClass(); - if (!reset && text_class_ == text_class) { + // we'll only update the layout list if the text class has changed + // 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) { set(owner_.view()->cursor().innerParagraph().layout()->name()); return; } + inset_ = inset; text_class_ = text_class; - setUpdatesEnabled(false); - clear(); - - TextClass::const_iterator it = text_class_->begin(); - TextClass::const_iterator const end = text_class_->end(); - for (; it != end; ++it) { - // ignore obsolete entries - addItemSort(toqstr(translateIfPossible((*it)->name())), lyxrc.sort_layouts); + model_->clear(); + for (size_t i = 0; i != text_class_->layoutCount(); ++i) { + Layout const & lt = *text_class_->layout(i); + docstring const & name = lt.name(); + // if this inset requires the empty layout, we skip the default + // layout + if (name == text_class_->defaultLayoutName() && inset && + (inset->forceEmptyLayout() || inset->useEmptyLayout())) + continue; + // if it doesn't require the empty layout, we skip it + if (name == text_class_->emptyLayoutName() && inset && + !inset->forceEmptyLayout() && !inset->useEmptyLayout()) + continue; + addItemSort(name, lyxrc.sort_layouts); } - setCurrentIndex(0); + set(owner_.view()->cursor().innerParagraph().layout()->name()); // needed to recalculate size hint hide(); setMinimumWidth(sizeHint().width()); - set(owner_.view()->cursor().innerParagraph().layout()->name()); + setEnabled(!buffer->isReadonly()); show(); - - setUpdatesEnabled(true); } -void GuiLayoutBox::selected(const QString & str) +void GuiLayoutBox::selected(int index) { + // get selection + QModelIndex mindex = filterModel_->mapToSource(filterModel_->index(index, 1)); + docstring const name = qstring_to_ucs4(model_->itemFromIndex(mindex)->text()); + owner_.setFocus(); - updateContents(false); - if (!text_class_) + + if (!text_class_) { + updateContents(false); + resetFilter(); return; + } - docstring const name = qstring_to_ucs4(str); - TextClass::const_iterator it = text_class_->begin(); - TextClass::const_iterator const end = text_class_->end(); - for (; it != end; ++it) { - docstring const & itname = (*it)->name(); - if (translateIfPossible(itname) == name) { + // find corresponding text class + for (size_t i = 0; i != text_class_->layoutCount(); ++i) { + docstring const & itname = text_class_->layout(i)->name(); + if (itname == name) { FuncRequest const func(LFUN_LAYOUT, itname, FuncRequest::TOOLBAR); theLyXFunc().setLyXView(&owner_); lyx::dispatch(func); + updateContents(false); + resetFilter(); return; } } @@ -389,6 +648,115 @@ Action * GuiToolbar::addItem(ToolbarItem const & item) return act; } +namespace { + +class PaletteButton : public QToolButton +{ +private: + GuiToolbar * bar_; + ToolbarItem const & tbitem_; + bool initialized_; +public: + PaletteButton(GuiToolbar * bar, ToolbarItem const & item) + : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false) + { + QString const label = qt_(to_ascii(tbitem_.label_)); + setToolTip(label); + setStatusTip(label); + setText(label); + connect(bar_, SIGNAL(iconSizeChanged(QSize)), + this, SLOT(setIconSize(QSize))); + setCheckable(true); + ToolbarInfo const * tbinfo = + toolbarbackend.getDefinedToolbarInfo(tbitem_.name_); + if (tbinfo) + // use the icon of first action for the toolbar button + setIcon(getIcon(tbinfo->items.begin()->func_, true)); + } + + void mousePressEvent(QMouseEvent * e) + { + if (initialized_) { + QToolButton::mousePressEvent(e); + return; + } + + initialized_ = true; + + ToolbarInfo const * tbinfo = + toolbarbackend.getDefinedToolbarInfo(tbitem_.name_); + if (!tbinfo) { + lyxerr << "Unknown toolbar " << tbitem_.name_ << endl; + return; + } + IconPalette * panel = new IconPalette(this); + QString const label = qt_(to_ascii(tbitem_.label_)); + panel->setWindowTitle(label); + connect(this, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool))); + connect(panel, SIGNAL(visible(bool)), this, SLOT(setChecked(bool))); + ToolbarInfo::item_iterator it = tbinfo->items.begin(); + ToolbarInfo::item_iterator const end = tbinfo->items.end(); + for (; it != end; ++it) + if (!getStatus(it->func_).unknown()) + panel->addButton(bar_->addItem(*it)); + + QToolButton::mousePressEvent(e); + } +}; + +class MenuButton : public QToolButton +{ +private: + GuiToolbar * bar_; + ToolbarItem const & tbitem_; + bool initialized_; +public: + MenuButton(GuiToolbar * bar, ToolbarItem const & item) + : QToolButton(bar), bar_(bar), tbitem_(item), initialized_(false) + { + setPopupMode(QToolButton::InstantPopup); + QString const label = qt_(to_ascii(tbitem_.label_)); + setToolTip(label); + setStatusTip(label); + setText(label); + setIcon(QPixmap(":images/math/" + toqstr(tbitem_.name_) + ".png")); + connect(bar, SIGNAL(iconSizeChanged(QSize)), + this, SLOT(setIconSize(QSize))); + } + + void mousePressEvent(QMouseEvent * e) + { + if (initialized_) { + QToolButton::mousePressEvent(e); + return; + } + + initialized_ = true; + + QString const label = qt_(to_ascii(tbitem_.label_)); + ButtonMenu * m = new ButtonMenu(label, this); + m->setWindowTitle(label); + m->setTearOffEnabled(true); + connect(bar_, SIGNAL(updated()), m, SLOT(updateParent())); + ToolbarInfo const * tbinfo = + toolbarbackend.getDefinedToolbarInfo(tbitem_.name_); + if (!tbinfo) { + lyxerr << "Unknown toolbar " << tbitem_.name_ << endl; + return; + } + ToolbarInfo::item_iterator it = tbinfo->items.begin(); + ToolbarInfo::item_iterator const end = tbinfo->items.end(); + for (; it != end; ++it) + if (!getStatus(it->func_).unknown()) + m->add(bar_->addItem(*it)); + setMenu(m); + + QToolButton::mousePressEvent(e); + } +}; + +} + void GuiToolbar::add(ToolbarItem const & item) { @@ -410,9 +778,10 @@ void GuiToolbar::add(ToolbarItem const & item) QToolButton * tb = new QToolButton; tb->setCheckable(true); tb->setIcon(getIcon(FuncRequest(LFUN_TABULAR_INSERT), true)); - tb->setToolTip(qt_(to_ascii(item.label_))); - tb->setStatusTip(qt_(to_ascii(item.label_))); - tb->setText(qt_(to_ascii(item.label_))); + QString const label = qt_(to_ascii(item.label_)); + tb->setToolTip(label); + tb->setStatusTip(label); + tb->setText(label); InsertTableWidget * iv = new InsertTableWidget(owner_, tb); connect(tb, SIGNAL(clicked(bool)), iv, SLOT(show(bool))); connect(iv, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool))); @@ -420,62 +789,12 @@ void GuiToolbar::add(ToolbarItem const & item) addWidget(tb); break; } - case ToolbarItem::ICONPALETTE: { - QToolButton * tb = new QToolButton(this); - tb->setToolTip(qt_(to_ascii(item.label_))); - tb->setStatusTip(qt_(to_ascii(item.label_))); - tb->setText(qt_(to_ascii(item.label_))); - connect(this, SIGNAL(iconSizeChanged(QSize)), - tb, SLOT(setIconSize(QSize))); - IconPalette * panel = new IconPalette(tb); - panel->setWindowTitle(qt_(to_ascii(item.label_))); - connect(this, SIGNAL(updated()), panel, SLOT(updateParent())); - ToolbarInfo const * tbinfo = toolbarbackend.getDefinedToolbarInfo(item.name_); - if (!tbinfo) { - lyxerr << "Unknown toolbar " << item.name_ << endl; - break; - } - ToolbarInfo::item_iterator it = tbinfo->items.begin(); - ToolbarInfo::item_iterator const end = tbinfo->items.end(); - for (; it != end; ++it) - if (!getStatus(it->func_).unknown()) { - panel->addButton(addItem(*it)); - // use the icon of first action for the toolbar button - if (it == tbinfo->items.begin()) - tb->setIcon(getIcon(it->func_, true)); - } - tb->setCheckable(true); - connect(tb, SIGNAL(clicked(bool)), panel, SLOT(setVisible(bool))); - connect(panel, SIGNAL(visible(bool)), tb, SLOT(setChecked(bool))); - addWidget(tb); + case ToolbarItem::ICONPALETTE: + addWidget(new PaletteButton(this, item)); break; - } + case ToolbarItem::POPUPMENU: { - QToolButton * tb = new QToolButton; - tb->setPopupMode(QToolButton::InstantPopup); - tb->setToolTip(qt_(to_ascii(item.label_))); - tb->setStatusTip(qt_(to_ascii(item.label_))); - tb->setText(qt_(to_ascii(item.label_))); - tb->setIcon(QPixmap(":images/math/" + toqstr(item.name_) + ".png")); - connect(this, SIGNAL(iconSizeChanged(QSize)), - tb, SLOT(setIconSize(QSize))); - - ButtonMenu * m = new ButtonMenu(qt_(to_ascii(item.label_)), tb); - m->setWindowTitle(qt_(to_ascii(item.label_))); - m->setTearOffEnabled(true); - connect(this, SIGNAL(updated()), m, SLOT(updateParent())); - ToolbarInfo const * tbinfo = toolbarbackend.getDefinedToolbarInfo(item.name_); - if (!tbinfo) { - lyxerr << "Unknown toolbar " << item.name_ << endl; - break; - } - ToolbarInfo::item_iterator it = tbinfo->items.begin(); - ToolbarInfo::item_iterator const end = tbinfo->items.end(); - for (; it != end; ++it) - if (!getStatus(it->func_).unknown()) - m->add(addItem(*it)); - tb->setMenu(m); - addWidget(tb); + addWidget(new MenuButton(this, item)); break; } case ToolbarItem::COMMAND: {