#include <config.h>
-#include "GuiWorkArea.h"
+#include "GuiCompleter.h"
#include "Buffer.h"
#include "BufferView.h"
+#include "CompletionList.h"
#include "Cursor.h"
#include "Dimension.h"
-#include "FuncRequest.h"
+#include "GuiWorkArea.h"
#include "GuiView.h"
-#include "LyXFunc.h"
+#include "LyX.h"
#include "LyXRC.h"
#include "Paragraph.h"
+#include "qt_helpers.h"
#include "version.h"
+#include "support/lassert.h"
+#include "support/lstrings.h"
#include "support/debug.h"
#include <QApplication>
-#include <QAbstractListModel>
#include <QHeaderView>
+#include <QKeyEvent>
#include <QPainter>
#include <QPixmapCache>
#include <QScrollBar>
namespace lyx {
namespace frontend {
-class RtlItemDelegate : public QItemDelegate {
+class CompleterItemDelegate : public QItemDelegate
+{
public:
- explicit RtlItemDelegate(QObject * parent = 0)
- : QItemDelegate(parent) {}
-
-protected:
- virtual void drawDisplay(QPainter * painter,
- QStyleOptionViewItem const & option,
- QRect const & rect, QString const & text) const
- {
- // FIXME: do this more elegantly
- docstring stltext = qstring_to_ucs4(text);
- reverse(stltext.begin(), stltext.end());
- QItemDelegate::drawDisplay(painter, option, rect, toqstr(stltext));
- }
-};
-
+ explicit CompleterItemDelegate(QObject * parent)
+ : QItemDelegate(parent)
+ {}
-class PixmapItemDelegate : public QItemDelegate {
-public:
- explicit PixmapItemDelegate(QObject *parent = 0)
- : QItemDelegate(parent) {}
+ ~CompleterItemDelegate()
+ {}
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
+ if (index.column() == 0) {
+ QItemDelegate::paint(painter, option, index);
+ return;
+ }
QStyleOptionViewItem opt = setOptions(index, option);
QVariant value = index.data(Qt::DisplayRole);
QPixmap pixmap = qvariant_cast<QPixmap>(value);
}
};
-
-class GuiCompletionModel : public QAbstractListModel {
+class GuiCompletionModel : public QAbstractListModel
+{
public:
///
- GuiCompletionModel(QObject * parent,
- Inset::CompletionList const * l)
- : QAbstractListModel(parent), list_(l) {}
+ GuiCompletionModel(QObject * parent, CompletionList const * l)
+ : QAbstractListModel(parent), list_(l)
+ {}
///
- ~GuiCompletionModel()
- { delete list_; }
+ ~GuiCompletionModel() { delete list_; }
+ ///
+ void setList(CompletionList const * l) {
+ beginResetModel();
+ delete list_;
+ list_ = l;
+ endResetModel();
+ }
///
bool sorted() const
{
if (list_)
return list_->sorted();
- else
- return false;
+ return false;
}
///
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
{
if (list_ == 0)
return 0;
- else
- return list_->size();
+ return list_->size();
}
///
if (index.column() == 0)
return toqstr(list_->data(index.row()));
- else if (index.column() == 1) {
- // get icon from cache
- QPixmap scaled;
- QString const name = ":" + toqstr(list_->icon(index.row()));
- if (!QPixmapCache::find("completion" + name, scaled)) {
- // load icon from disk
- QPixmap p = QPixmap(name);
- if (!p.isNull()) {
- // scale it to 16x16 or smaller
- scaled = p.scaled(min(16, p.width()), min(16, p.height()),
- Qt::KeepAspectRatio, Qt::SmoothTransformation);
- }
- QPixmapCache::insert("completion" + name, scaled);
- }
+ if (index.column() != 1)
+ return QVariant();
+
+ // get icon from cache
+ QPixmap scaled;
+ QString const name = ":" + toqstr(list_->icon(index.row()));
+ if (name == ":")
return scaled;
+ if (!QPixmapCache::find("completion" + name, scaled)) {
+ // load icon from disk
+ QPixmap p = QPixmap(name);
+ if (!p.isNull()) {
+ // scale it to 16x16 or smaller
+ scaled = p.scaled(min(16, p.width()), min(16, p.height()),
+ Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ }
+ QPixmapCache::insert("completion" + name, scaled);
}
- return QVariant();
+ return scaled;
}
private:
- ///
- Inset::CompletionList const * list_;
+ /// owned by us
+ CompletionList const * list_;
};
GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
- : QCompleter(parent), gui_(gui), updateLock_(0),
- inlineVisible_(false)
+ : QCompleter(parent), gui_(gui), old_cursor_(0), updateLock_(0),
+ inlineVisible_(false), popupVisible_(false),
+ modelActive_(false)
{
// Setup the completion popup
- setModel(new GuiCompletionModel(this, 0));
+ model_ = new GuiCompletionModel(this, 0);
+ setModel(model_);
setCompletionMode(QCompleter::PopupCompletion);
+ setCaseSensitivity(Qt::CaseSensitive);
setWidget(gui_);
// create the popup
QTreeView *listView = new QTreeView;
- listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
- listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- listView->setSelectionBehavior(QAbstractItemView::SelectRows);
- listView->setSelectionMode(QAbstractItemView::SingleSelection);
+ listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ listView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ listView->setSelectionMode(QAbstractItemView::SingleSelection);
listView->header()->hide();
listView->setIndentation(0);
listView->setUniformRowHeights(true);
setPopup(listView);
- popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
- rtlItemDelegate_ = new RtlItemDelegate(this);
+
+ itemDelegate_ = new CompleterItemDelegate(this);
+ popup()->setItemDelegate(itemDelegate_);
// create timeout timers
popup_timer_.setSingleShot(true);
}
+bool GuiCompleter::uniqueCompletionAvailable() const
+{
+ if (!modelActive_)
+ return false;
+
+ size_t n = popup()->model()->rowCount();
+ if (n > 1 || n == 0)
+ return false;
+
+ // if there is exactly one, we have to check whether it is a
+ // real completion, i.e. longer than the current prefix.
+ if (completionPrefix() == currentCompletion())
+ return false;
+
+ return true;
+}
+
+
+bool GuiCompleter::completionAvailable() const
+{
+ if (!modelActive_)
+ return false;
+
+ size_t n = popup()->model()->rowCount();
+
+ // if there is exactly one, we have to check whether it is a
+ // real completion, i.e. longer than the current prefix.
+ if (n == 1 && completionPrefix() == currentCompletion())
+ return false;
+
+ return n > 0;
+}
+
+
bool GuiCompleter::popupVisible() const
{
- return popup()->isVisible();
+ return popupVisible_;
}
}
-void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cursorInView)
+void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep)
{
// parameters which affect the completion
bool moved = cur != old_cursor_;
if (moved)
old_cursor_ = cur;
- bool possiblePopupState = popupPossible(cur) && cursorInView;
- bool possibleInlineState = inlinePossible(cur) && cursorInView;
+ bool const possiblePopupState = popupPossible(cur);
+ bool const possibleInlineState = inlinePossible(cur);
// we moved or popup state is not ok for popup?
if ((moved && !keep) || !possiblePopupState)
- hidePopup(cur);
+ hidePopup();
// we moved or inline state is not ok for inline completion?
if ((moved && !keep) || !possibleInlineState)
if (!inlineVisible() && possibleInlineState && start
&& cur.inset().automaticInlineCompletion())
inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
+ else if (cur.inMathed() && !lyxrc.completion_inline_math) {
+ // no inline completion, hence a metrics update is needed
+ if (!(cur.result().screenUpdate() & Update::Force))
+ cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
+ }
- // update prefix if popup is visible or if it will be visible soon
- if (popupVisible() || inlineVisible()
- || popup_timer_.isActive() || inline_timer_.isActive())
- updatePrefix(cur);
+ // update prefix if any completion is possible
+ bool modelActive = modelActive_ && model()->rowCount() > 0;
+ if (possiblePopupState || possibleInlineState) {
+ if (modelActive)
+ updatePrefix(cur);
+ else
+ updateAvailability();
+ }
}
void GuiCompleter::updateVisibility(bool start, bool keep)
{
Cursor cur = gui_->bufferView().cursor();
- cur.updateFlags(Update::None);
+ cur.screenUpdateFlags(Update::None);
updateVisibility(cur, start, keep);
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
}
-void GuiCompleter::updatePrefix(Cursor & cur)
+void GuiCompleter::updatePrefix(Cursor const & cur)
{
// get new prefix. Do nothing if unchanged
QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
// if popup is not empty, the new selection will
// be our last valid one
QString const & s = currentCompletion();
- if (s.length() > 0)
- last_selection_ = s;
- else
- last_selection_ = old;
-
+ if (popupVisible() || inlineVisible()) {
+ if (s.length() > 0)
+ last_selection_ = s;
+ else
+ last_selection_ = old;
+ }
+
// update inline completion because the default
// completion string might have changed
if (inlineVisible())
}
-void GuiCompleter::updateInline(Cursor & cur, QString const & completion)
+void GuiCompleter::updateInline(Cursor const & cur, QString const & completion)
{
if (!cur.inset().inlineCompletionSupported(cur))
return;
// compute postfix
docstring prefix = cur.inset().completionPrefix(cur);
- docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
+ docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
// shorten it if necessary
- if (lyxrc.completion_inline_dots != -1
- && postfix.size() > unsigned(lyxrc.completion_inline_dots))
- postfix = postfix.substr(0, lyxrc.completion_inline_dots - 1) + "...";
+ if (lyxrc.completion_inline_dots != -1)
+ support::truncateWithEllipsis(postfix,
+ unsigned(lyxrc.completion_inline_dots));
// set inline completion at cursor position
size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
}
-void GuiCompleter::updatePopup(Cursor & cur)
+void GuiCompleter::updatePopup(Cursor const & cur)
{
if (!cur.inset().completionSupported(cur))
return;
- if (completionCount() == 0)
+ popupVisible_ = true;
+
+ if (completionCount() == 0) {
+ QTimer::singleShot(0, popup(), SLOT(hide()));
return;
-
+ }
+
+ QTimer::singleShot(0, this, SLOT(asyncUpdatePopup()));
+}
+
+
+void GuiCompleter::asyncUpdatePopup()
+{
+ Cursor cur = gui_->bufferView().cursor();
+ if (!cur.inset().completionSupported(cur)
+ || !cur.bv().paragraphVisible(cur)) {
+ popupVisible_ = false;
+ return;
+ }
+
// get dimensions of completion prefix
Dimension dim;
int x;
else
rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
+ // Resize the columns in the popup.
+ // This should really be in the constructor. But somehow the treeview
+ // has a bad memory about it and we have to tell him again and again.
+ QTreeView * listView = static_cast<QTreeView *>(popup());
+ listView->header()->setStretchLastSection(false);
+ setSectionResizeMode(listView->header(), 0, QHeaderView::Stretch);
+ setSectionResizeMode(listView->header(), 1, QHeaderView::Fixed);
+ listView->header()->resizeSection(1, 22);
+
// show/update popup
complete(rect);
- QTreeView * p = static_cast<QTreeView *>(popup());
- p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width());
}
-void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate)
+void GuiCompleter::updateAvailability()
+{
+ // this should really only be of interest if no completion is
+ // visible yet, i.e. especially if automatic completion is disabled.
+ if (inlineVisible() || popupVisible())
+ return;
+ Cursor const & cur = gui_->bufferView().cursor();
+ if (!popupPossible(cur) && !inlinePossible(cur))
+ return;
+
+ updateModel(cur, false, false);
+}
+
+
+void GuiCompleter::updateModel(Cursor const & cur, bool popupUpdate, bool inlineUpdate)
{
// value which should be kept selected
QString old = currentCompletion();
bool rtl = false;
if (cur.inTexted()) {
Paragraph const & par = cur.paragraph();
- Font const font =
- par.getFontSettings(cur.bv().buffer().params(), cur.pos());
+ Font const & font =
+ par.getFontSettings(cur.bv().buffer().params(), cur.pos());
rtl = font.isVisibleRightToLeft();
}
popup()->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight);
- // turn the direction of the strings in the popup.
- // Qt does not do that itself.
- popup()->setItemDelegateForColumn(0, rtl ? rtlItemDelegate_ : 0);
-
// set new model
- Inset::CompletionList const * list = cur.inset().createCompletionList(cur);
- setModel(new GuiCompletionModel(this, list));
+ CompletionList const * list = cur.inset().createCompletionList(cur);
+ model_->setList(list);
+ modelActive_ = true;
if (list->sorted())
setModelSorting(QCompleter::CaseSensitivelySortedModel);
else
// if popup is not empty, the new selection will
// be our last valid one
- QString const & s = currentCompletion();
- if (s.length() > 0)
- last_selection_ = s;
- else
- last_selection_ = old;
+ if (popupVisible() || inlineVisible()) {
+ QString const & s = currentCompletion();
+ if (s.length() > 0)
+ last_selection_ = s;
+ else
+ last_selection_ = old;
+ }
// show inline completion
if (inlineUpdate)
}
-void GuiCompleter::showPopup(Cursor & cur)
+void GuiCompleter::showPopup(Cursor const & cur)
{
if (!popupPossible(cur))
return;
}
-void GuiCompleter::hidePopup(Cursor & cur)
+void GuiCompleter::asyncHidePopup()
{
popup()->hide();
- if (popup_timer_.isActive())
- popup_timer_.stop();
+ if (!inlineVisible())
+ model_->setList(0);
}
-void GuiCompleter::showInline(Cursor & cur)
+void GuiCompleter::showInline(Cursor const & cur)
{
if (!inlinePossible(cur))
return;
}
-void GuiCompleter::hideInline(Cursor & cur)
+void GuiCompleter::hideInline(Cursor const & cur)
{
- gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring());
+ gui_->bufferView().setInlineCompletion(cur, DocIterator(cur.buffer()), docstring());
inlineVisible_ = false;
+
+ if (inline_timer_.isActive())
+ inline_timer_.stop();
+
+ // Trigger asynchronous part of hideInline. We might be
+ // in a dispatcher here and the setModel call might
+ // trigger focus events which is are not healthy here.
+ QTimer::singleShot(0, this, SLOT(asyncHideInline()));
+
+ // mark that the asynchronous part will reset the model
+ if (!popupVisible())
+ modelActive_ = false;
+}
+
+
+void GuiCompleter::asyncHideInline()
+{
+ if (!popupVisible())
+ model_->setList(0);
}
void GuiCompleter::showPopup()
{
Cursor cur = gui_->bufferView().cursor();
- cur.updateFlags(Update::None);
+ cur.screenUpdateFlags(Update::None);
showPopup(cur);
// redraw if needed
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
}
void GuiCompleter::showInline()
{
Cursor cur = gui_->bufferView().cursor();
- cur.updateFlags(Update::None);
+ cur.screenUpdateFlags(Update::None);
showInline(cur);
// redraw if needed
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
+}
+
+
+void GuiCompleter::hidePopup()
+{
+ popupVisible_ = false;
+
+ if (popup_timer_.isActive())
+ popup_timer_.stop();
+
+ // hide popup asynchronously because we might be here inside of
+ // LFUN dispatchers. Hiding a popup can trigger a focus event on the
+ // workarea which then redisplays the cursor. But the metrics are not
+ // yet up to date such that the coord cache has not all insets yet. The
+ // cursorPos methods would triggers asserts in the coord cache then.
+ QTimer::singleShot(0, this, SLOT(asyncHidePopup()));
+
+ // mark that the asynchronous part will reset the model
+ if (!inlineVisible())
+ modelActive_ = false;
+}
+
+
+void GuiCompleter::hideInline()
+{
+ Cursor cur = gui_->bufferView().cursor();
+ cur.screenUpdateFlags(Update::None);
+
+ hideInline(cur);
+
+ // redraw if needed
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
}
void GuiCompleter::activate()
{
if (!popupVisible() && !inlineVisible())
- return;
-
- popupActivated(currentCompletion());
+ tab();
+ else
+ popupActivated(currentCompletion());
}
{
BufferView * bv = &gui_->bufferView();
Cursor cur = bv->cursor();
- cur.updateFlags(Update::None);
+ cur.screenUpdateFlags(Update::None);
// check that inline completion is active
- if (!inlineVisible()) {
+ if (!inlineVisible() && !uniqueCompletionAvailable()) {
// try to activate the inline completion
if (cur.inset().inlineCompletionSupported(cur)) {
showInline();
return;
}
+ // Make undo possible
+ cur.beginUndoGroup();
+ cur.recordUndo();
+
// If completion is active, at least complete by one character
docstring prefix = cur.inset().completionPrefix(cur);
- docstring completion = from_utf8(fromqstr(currentCompletion()));
+ docstring completion = qstring_to_ucs4(currentCompletion());
if (completion.size() <= prefix.size()) {
// finalize completion
cur.inset().insertCompletion(cur, docstring(), true);
// hide popup and inline completion
- hidePopup(cur);
+ hidePopup();
hideInline(cur);
updateVisibility(false, false);
+ cur.endUndoGroup();
return;
}
docstring nextchar = completion.substr(prefix.size(), 1);
- if (!cur.inset().insertCompletion(cur, nextchar, false))
+ if (!cur.inset().insertCompletion(cur, nextchar, false)) {
+ cur.endUndoGroup();
return;
+ }
updatePrefix(cur);
// try to complete as far as it is unique
popup_timer_.start(0);
// redraw if needed
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
+ cur.endUndoGroup();
}
}
} else {
// In sorted models, do binary search for s.
- i = 0;
- size_t r = n - 1;
- while (r >= i && i < n) {
- size_t mid = (r + i) / 2;
+ int l = 0;
+ int r = n - 1;
+ while (r >= l && l < int(n)) {
+ size_t mid = (r + l) / 2;
QString const & mids
= model.data(model.index(mid, 0),
Qt::EditRole).toString();
// from the CompletionList has?
int c = s.compare(mids, Qt::CaseSensitive);
if (c == 0) {
- i = mid;
+ l = mid;
break;
- } else if (i == r) {
- i = n;
+ } else if (l == r) {
+ l = n;
break;
} else if (c > 0)
// middle is not far enough
- i = mid + 1;
+ l = mid + 1;
else
// middle is too far
r = mid - 1;
}
// loop was left without finding anything
- if (r < i)
+ if (r < l)
i = n;
+ else
+ i = l;
+ // we can try to recover
+ LASSERT(i <= n, i = 0);
}
// select the first if none was found
docstring GuiCompleter::longestUniqueCompletion() const
{
QAbstractItemModel const & model = *popup()->model();
- QString s = currentCompletion();
size_t n = model.rowCount();
+ if (n == 0)
+ return docstring();
+ QString s = model.data(model.index(0, 0), Qt::EditRole).toString();
if (modelSorting() == QCompleter::UnsortedModel) {
// For unsorted model we cannot do more than iteration.
}
}
- return from_utf8(fromqstr(s));
+ return qstring_to_ucs4(s);
}
void GuiCompleter::popupActivated(const QString & completion)
{
Cursor cur = gui_->bufferView().cursor();
- cur.updateFlags(Update::None);
-
+ cur.screenUpdateFlags(Update::None);
+
+ cur.beginUndoGroup();
+ cur.recordUndo();
+
docstring prefix = cur.inset().completionPrefix(cur);
- docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
+ docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
cur.inset().insertCompletion(cur, postfix, true);
- hidePopup(cur);
+ hidePopup();
hideInline(cur);
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
+ cur.endUndoGroup();
}
return;
Cursor cur = gui_->bufferView().cursor();
- cur.updateFlags(Update::None);
+ cur.screenUpdateFlags(Update::None);
if (inlineVisible())
updateInline(cur, completion);
- if (cur.disp_.update())
- gui_->bufferView().processUpdateFlags(cur.disp_.update());
+ if (cur.result().screenUpdate())
+ gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
}
} // namespace frontend
} // namespace lyx
-#include "GuiCompleter_moc.cpp"
+#include "moc_GuiCompleter.cpp"