X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Ffrontends%2Fqt4%2FGuiCompleter.cpp;h=33bc42465137194ed0c4460ffdd00a262064c9d4;hb=425d092204118ea6c24c28e85fdf03fcf2bb51a4;hp=8aee5c1d47b4ac2015900f243ae6ba731c9272fa;hpb=2c62c9d91c2f60ae7c70f21316afaa8740db75f7;p=lyx.git diff --git a/src/frontends/qt4/GuiCompleter.cpp b/src/frontends/qt4/GuiCompleter.cpp index 8aee5c1d47..33bc424651 100644 --- a/src/frontends/qt4/GuiCompleter.cpp +++ b/src/frontends/qt4/GuiCompleter.cpp @@ -10,23 +10,25 @@ #include -#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 "LyXRC.h" #include "Paragraph.h" #include "version.h" +#include "support/lassert.h" #include "support/debug.h" #include -#include #include #include #include @@ -41,33 +43,24 @@ using namespace lyx::support; 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(value); @@ -86,23 +79,27 @@ protected: } }; - -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) { + delete list_; + list_ = l; + reset(); + } /// bool sorted() const { if (list_) return list_->sorted(); - else - return false; + return false; } /// int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const @@ -132,7 +129,8 @@ public: if (index.column() == 0) return toqstr(list_->data(index.row())); - else if (index.column() == 1) { + + if (index.column() == 1) { // get icon from cache QPixmap scaled; QString const name = ":" + toqstr(list_->icon(index.row())); @@ -153,18 +151,21 @@ public: } 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::CaseInsensitive); setWidget(gui_); // create the popup @@ -177,8 +178,9 @@ GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent) 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); @@ -233,9 +235,43 @@ bool GuiCompleter::inlinePossible(Cursor const & cur) const } +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_; } @@ -262,28 +298,12 @@ void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cu bool possibleInlineState = inlinePossible(cur) && cursorInView; // we moved or popup state is not ok for popup? - if ((moved && !keep) || !possiblePopupState) { - // stop an old completion timer - if (popup_timer_.isActive()) - popup_timer_.stop(); - - // hide old popup - if (popupVisible()) - popup()->hide(); - } + if ((moved && !keep) || !possiblePopupState) + hidePopup(cur); // we moved or inline state is not ok for inline completion? - if ((moved && !keep) || !possibleInlineState) { - // stop an old completion timer - if (inline_timer_.isActive()) - inline_timer_.stop(); - - // hide old inline completion - if (inlineVisible()) { - gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring()); - inlineVisible_ = false; - } - } + if ((moved && !keep) || !possibleInlineState) + hideInline(cur); // we inserted something and are in a possible popup state? if (!popupVisible() && possiblePopupState && start @@ -294,11 +314,20 @@ void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cu if (!inlineVisible() && possibleInlineState && start && cur.inset().automaticInlineCompletion()) inline_timer_.start(int(lyxrc.completion_inline_delay * 1000)); + else { + // no inline completion, hence a metrics update is needed + if (!(cur.disp_.update() & Update::Force)) + cur.updateFlags(cur.disp_.update() | 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(); + } } @@ -339,11 +368,13 @@ void GuiCompleter::updatePrefix(Cursor & 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()) @@ -358,7 +389,7 @@ void GuiCompleter::updateInline(Cursor & cur, QString const & completion) // 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 @@ -377,9 +408,25 @@ void GuiCompleter::updatePopup(Cursor & 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)) { + popupVisible_ = false; + return; + } + // get dimensions of completion prefix Dimension dim; int x; @@ -393,13 +440,34 @@ void GuiCompleter::updatePopup(Cursor & cur) 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(popup()); + listView->header()->setStretchLastSection(false); + listView->header()->setResizeMode(0, QHeaderView::Stretch); + listView->header()->setResizeMode(1, QHeaderView::Fixed); + listView->header()->resizeSection(1, 22); + // show/update popup complete(rect); - QTreeView * p = static_cast(popup()); - p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width()); } +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 & cur = gui_->bufferView().cursor(); + if (!popupPossible(cur) && !inlinePossible(cur)) + return; + + updateModel(cur, false, false); +} + + void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate) { // value which should be kept selected @@ -411,19 +479,16 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate 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 @@ -443,11 +508,13 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate // 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) @@ -464,6 +531,34 @@ void GuiCompleter::showPopup(Cursor & cur) } +void GuiCompleter::hidePopup(Cursor &) +{ + 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::asyncHidePopup() +{ + popup()->hide(); + if (!inlineVisible()) + model_->setList(0); +} + + void GuiCompleter::showInline(Cursor & cur) { if (!inlinePossible(cur)) @@ -473,6 +568,32 @@ void GuiCompleter::showInline(Cursor & cur) } +void GuiCompleter::hideInline(Cursor & cur) +{ + 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(); @@ -499,15 +620,38 @@ void GuiCompleter::showInline() } +void GuiCompleter::hidePopup() +{ + Cursor cur = gui_->bufferView().cursor(); + cur.updateFlags(Update::None); + + hidePopup(cur); + + // redraw if needed + if (cur.disp_.update()) + gui_->bufferView().processUpdateFlags(cur.disp_.update()); +} + + +void GuiCompleter::hideInline() +{ + Cursor cur = gui_->bufferView().cursor(); + cur.updateFlags(Update::None); + + hideInline(cur); + + // redraw if needed + if (cur.disp_.update()) + gui_->bufferView().processUpdateFlags(cur.disp_.update()); +} + + void GuiCompleter::activate() { if (!popupVisible() && !inlineVisible()) - return; - - // Complete with current selection in the popup. - QString s = currentCompletion(); - popup()->hide(); - popupActivated(s); + tab(); + else + popupActivated(currentCompletion()); } @@ -518,7 +662,7 @@ void GuiCompleter::tab() cur.updateFlags(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(); @@ -540,17 +684,19 @@ void GuiCompleter::tab() return; } + // Make undo possible + 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 - popup()->hide(); - gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring()); - inlineVisible_ = false; + hidePopup(cur); + hideInline(cur); updateVisibility(false, false); return; } @@ -618,10 +764,10 @@ void GuiCompleter::setCurrentCompletion(QString const & s) } } 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(); @@ -631,22 +777,25 @@ void GuiCompleter::setCurrentCompletion(QString const & s) // 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; + LASSERT(i <= n, /**/); } // select the first if none was found @@ -676,8 +825,10 @@ size_t commonPrefix(QString const & s1, QString const & s2) 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. @@ -718,7 +869,7 @@ docstring GuiCompleter::longestUniqueCompletion() const } } - return from_utf8(fromqstr(s)); + return qstring_to_ucs4(s); } @@ -727,10 +878,13 @@ void GuiCompleter::popupActivated(const QString & completion) Cursor cur = gui_->bufferView().cursor(); cur.updateFlags(Update::None); + 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); - updateVisibility(cur, false); + hidePopup(cur); + hideInline(cur); if (cur.disp_.update()) gui_->bufferView().processUpdateFlags(cur.disp_.update()); @@ -755,4 +909,4 @@ void GuiCompleter::popupHighlighted(const QString & completion) } // namespace frontend } // namespace lyx -#include "GuiCompleter_moc.cpp" +#include "moc_GuiCompleter.cpp"