]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiCompleter.cpp
fix completion painting for RTL (inline completion and completion list)
[lyx.git] / src / frontends / qt4 / GuiCompleter.cpp
index fff172ff2c4a474d4b669819d07bd184d328385f..4b592dcad72961e8f3ed0c4de1aa19f45298442b 100644 (file)
 
 #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 "LyXRC.h"
+#include "Paragraph.h"
 #include "version.h"
 
+#include "support/lassert.h"
 #include "support/debug.h"
 
 #include <QApplication>
-#include <QAbstractListModel>
 #include <QHeaderView>
 #include <QPainter>
 #include <QPixmapCache>
@@ -40,56 +43,89 @@ using namespace lyx::support;
 namespace lyx {
 namespace frontend {
 
-
-class PixmapItemDelegate : public QItemDelegate {
+class CompleterItemDelegate : public QItemDelegate
+{
 public:
-       explicit PixmapItemDelegate(QObject *parent = 0)
-       : QItemDelegate(parent) {}
+       explicit CompleterItemDelegate(QObject * parent)
+               : QItemDelegate(parent)
+       {}
+
+       ~CompleterItemDelegate()
+       {}
 
 protected:
+       void drawDisplay(QPainter * painter,
+               QStyleOptionViewItem const & option,
+               QRect const & rect, QString const & text) const
+       {
+               QItemDelegate::drawDisplay(painter, option, rect, text);
+       }
+
        void paint(QPainter *painter, const QStyleOptionViewItem &option,
                   const QModelIndex &index) const
        {
-               QStyleOptionViewItemV3 opt = setOptions(index, option);
+               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);
-               const QSize size = pixmap.size();
-
+               
                // draw
                painter->save();
                drawBackground(painter, opt, index);
-               painter->drawPixmap(option.rect.left() + (16 - size.width()) / 2,
-                       option.rect.top() + (option.rect.height() - size.height()) / 2,
-                       pixmap);
+               if (!pixmap.isNull()) {
+                       const QSize size = pixmap.size();
+                       painter->drawPixmap(option.rect.left() + (16 - size.width()) / 2,
+                               option.rect.top() + (option.rect.height() - size.height()) / 2,
+                               pixmap);
+               }
                drawFocus(painter, opt, option.rect);
                painter->restore();
        }
 };
 
-
-class GuiCompletionModel : public QAbstractListModel {
+class GuiCompletionModel : public QAbstractListModel
+{
 public:
        ///
-       GuiCompletionModel(QObject * parent, Inset::CompletionListPtr l)
-       : QAbstractListModel(parent), list(l) {}
+       GuiCompletionModel(QObject * parent, CompletionList const * l)
+               : QAbstractListModel(parent), list_(l)
+       {}
+       ///
+       ~GuiCompletionModel() { delete list_; }
+       ///
+       void setList(CompletionList const * l) {
+               delete list_;
+               list_ = l;
+               reset();
+       }
+       ///
+       bool sorted() const
+       {
+               if (list_)
+                       return list_->sorted();
+               return false;
+       }
        ///
-       int columnCount(const QModelIndex & parent = QModelIndex()) const
+       int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
        {
                return 2;
        }
        ///
-       int rowCount(const QModelIndex & parent = QModelIndex()) const
+       int rowCount(const QModelIndex & /*parent*/ = QModelIndex()) const
        {
-               if (list.get() == 0)
+               if (list_ == 0)
                        return 0;
                else
-                       return list->size();
+                       return list_->size();
        }
 
        ///
        QVariant data(const QModelIndex & index, int role) const
        {
-               if (list.get() == 0)
+               if (list_ == 0)
                        return QVariant();
 
                if (index.row() < 0 || index.row() >= rowCount())
@@ -99,18 +135,21 @@ public:
                    return QVariant();
                    
                if (index.column() == 0)
-                       return toqstr(list->data(index.row()));
-               else if (index.column() == 1) {
+                       return toqstr(list_->data(index.row()));
+
+               if (index.column() == 1) {
                        // get icon from cache
                        QPixmap scaled;
-                       QString const name = ":" + toqstr(list->icon(index.row()));
+                       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);
+                               }
 
-                               // 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 scaled;
@@ -119,15 +158,19 @@ public:
        }
 
 private:
-       Inset::CompletionListPtr list;
+       /// owned by us
+       CompletionList const * list_;
 };
 
 
 GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
-       : QCompleter(parent), gui_(gui)
+       : QCompleter(parent), gui_(gui), updateLock_(0),
+         inlineVisible_(false), popupVisible_(false),
+         modelActive_(false)
 {
        // Setup the completion popup
-       setModel(new GuiCompletionModel(this, Inset::CompletionListPtr()));
+       model_ = new GuiCompletionModel(this, 0);
+       setModel(model_);
        setCompletionMode(QCompleter::PopupCompletion);
        setWidget(gui_);
        
@@ -139,8 +182,11 @@ GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
         listView->setSelectionMode(QAbstractItemView::SingleSelection);
        listView->header()->hide();
        listView->setIndentation(0);
+       listView->setUniformRowHeights(true);
        setPopup(listView);
-       popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(popup()));
+       
+       itemDelegate_ = new CompleterItemDelegate(this);
+       popup()->setItemDelegate(itemDelegate_);
        
        // create timeout timers
        popup_timer_.setSingleShot(true);
@@ -195,15 +241,37 @@ bool GuiCompleter::inlinePossible(Cursor const & cur) const
 }
 
 
+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_;
 }
 
 
 bool GuiCompleter::inlineVisible() const
 {
-       return !gui_->bufferView().inlineCompletionPos().empty();
+       // In fact using BufferView::inlineCompletionPos.empty() should be
+       // here. But unfortunately this information is not good enough
+       // because destructive operations like backspace might invalidate
+       // inlineCompletionPos. But then the completion should stay visible
+       // (i.e. reshown on the next update). Hence be keep this information
+       // in the inlineVisible_ variable.
+       return inlineVisible_;
 }
 
 
@@ -218,50 +286,41 @@ 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(DocIterator(), docstring());
-                       cur.updateFlags(Update::Force | Update::SinglePar);
-               }
-       }
+       if ((moved && !keep) || !possibleInlineState)
+               hideInline(cur);
 
        // we inserted something and are in a possible popup state?
        if (!popupVisible() && possiblePopupState && start
                && cur.inset().automaticPopupCompletion())
-               popup_timer_.start(lyxrc.completion_popup_delay * 1000.0);
+               popup_timer_.start(int(lyxrc.completion_popup_delay * 1000));
 
        // we inserted something and are in a possible inline completion state?
        if (!inlineVisible() && possibleInlineState && start
                && cur.inset().automaticInlineCompletion())
-               inline_timer_.start(lyxrc.completion_inline_delay * 1000.0);
+               inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
 
-       // 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);
+       
        updateVisibility(cur, start, keep);
+       
        if (cur.disp_.update())
                gui_->bufferView().processUpdateFlags(cur.disp_.update());
 }
@@ -281,7 +340,7 @@ void GuiCompleter::updatePrefix(Cursor & cur)
        
        // update completer to new prefix
        setCompletionPrefix(newPrefix);
-       
+
        // update popup because its size might have changed
        if (popupVisible())
                updatePopup(cur);
@@ -292,11 +351,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())
@@ -311,7 +372,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
@@ -320,8 +381,8 @@ void GuiCompleter::updateInline(Cursor & cur, QString const & completion)
 
        // set inline completion at cursor position
        size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
-       gui_->bufferView().setInlineCompletion(cur, postfix, uniqueTo - prefix.size());
-       cur.updateFlags(Update::Force | Update::SinglePar);
+       gui_->bufferView().setInlineCompletion(cur, cur, postfix, uniqueTo - prefix.size());
+       inlineVisible_ = true;
 }
 
 
@@ -330,36 +391,98 @@ 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;
        int y;
        cur.inset().completionPosAndDim(cur, x, y, dim);
-       QRect insetRect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
        
-       // show/update popup
-       complete(insetRect);
-       QTreeView * p = static_cast<QTreeView *>(popup());
-       p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width());
+       // and calculate the rect of the popup
+       QRect rect;
+       if (popup()->layoutDirection() == Qt::RightToLeft)
+               rect = QRect(x + dim.width() - 200, y - dim.ascent() - 3, 200, dim.height() + 6);
+       else
+               rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
        
-       // update highlight
-       updateInline(cur, currentCompletion());
+       // 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);
+       listView->header()->setResizeMode(0, QHeaderView::Stretch);
+       listView->header()->setResizeMode(1, QHeaderView::Fixed);
+       listView->header()->resizeSection(1, 22);
+       
+       // show/update popup
+       complete(rect);
 }
 
 
+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
        QString old = currentCompletion();
        if (old.length() == 0)
                old = last_selection_;
-       
+
+
+       // set whether rtl
+       bool rtl = false;
+       if (cur.inTexted()) {
+               Paragraph const & par = cur.paragraph();
+               Font const font =
+                       par.getFontSettings(cur.bv().buffer().params(), cur.pos());
+               rtl = font.isVisibleRightToLeft();
+       }
+       popup()->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight);
+
        // set new model
-       setModel(new GuiCompletionModel(this, cur.inset().completionList(cur)));
-       
+       CompletionList const * list = cur.inset().createCompletionList(cur);
+       model_->setList(list);
+       modelActive_ = true;
+       if (list->sorted())
+               setModelSorting(QCompleter::CaseSensitivelySortedModel);
+       else
+               setModelSorting(QCompleter::UnsortedModel);
+
+       // set prefix
+       QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
+       if (newPrefix != completionPrefix())
+               setCompletionPrefix(newPrefix);
+
        // show popup
        if (popupUpdate)
                updatePopup(cur);
@@ -369,12 +492,14 @@ 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)
                updateInline(cur, currentCompletion());
@@ -387,9 +512,36 @@ void GuiCompleter::showPopup(Cursor & cur)
                return;
        
        updateModel(cur, true, inlineVisible());
-       updatePrefix(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)
 {
@@ -397,13 +549,40 @@ void GuiCompleter::showInline(Cursor & cur)
                return;
        
        updateModel(cur, popupVisible(), true);
-       updatePrefix(cur);
+}
+
+
+void GuiCompleter::hideInline(Cursor & cur)
+{
+       gui_->bufferView().setInlineCompletion(cur, DocIterator(), 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);
+       
        showPopup(cur);
 
        // redraw if needed
@@ -415,6 +594,8 @@ void GuiCompleter::showPopup()
 void GuiCompleter::showInline()
 {
        Cursor cur = gui_->bufferView().cursor();
+       cur.updateFlags(Update::None);
+       
        showInline(cur);
 
        // redraw if needed
@@ -423,28 +604,59 @@ 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);
+       popupActivated(currentCompletion());
 }
 
 
 void GuiCompleter::tab()
 {
        BufferView * bv = &gui_->bufferView();
-       Cursor & cur = bv->cursor();
-
+       Cursor cur = bv->cursor();
+       cur.updateFlags(Update::None);
+       
        // check that inline completion is active
        if (!inlineVisible()) {
                // try to activate the inline completion
                if (cur.inset().inlineCompletionSupported(cur)) {
                        showInline();
+                       
+                       // show popup without delay because the completion was not unique
+                       if (lyxrc.completion_popup_after_complete
+                           && !popupVisible()
+                           && popup()->model()->rowCount() > 1)
+                               popup_timer_.start(0);
+
                        return;
                }
                // or try popup
@@ -458,11 +670,14 @@ void GuiCompleter::tab()
        
        // 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);
-               popup()->hide();
+               
+               // hide popup and inline completion
+               hidePopup(cur);
+               hideInline(cur);
                updateVisibility(false, false);
                return;
        }
@@ -504,7 +719,7 @@ QString GuiCompleter::currentCompletion() const
 
 
 void GuiCompleter::setCurrentCompletion(QString const & s)
-{
+{      
        QAbstractItemModel const & model = *popup()->model();
        size_t n = model.rowCount();
        if (n == 0)
@@ -512,60 +727,144 @@ void GuiCompleter::setCurrentCompletion(QString const & s)
 
        // select the first if s is empty
        if (s.length() == 0) {
+               updateLock_++;
                popup()->setCurrentIndex(model.index(0, 0));
+               updateLock_--;
                return;
        }
 
-       // iterate through list until the s is found
-       // FIXME: there must be a better way than this iteration
+       // find old selection in model
        size_t i;
-       for (i = 0; i < n; ++i) {
-               QString const & is
-               = model.data(model.index(i, 0), Qt::EditRole).toString();
-               if (is == s)
-                       break;
+       if (modelSorting() == QCompleter::UnsortedModel) {
+               // In unsorted models, iterate through list until the s is found
+               for (i = 0; i < n; ++i) {
+                       QString const & is
+                       = model.data(model.index(i, 0), Qt::EditRole).toString();
+                       if (is == s)
+                               break;
+               }
+       } else {
+               // In sorted models, do binary search for s.
+               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();
+
+                       // left or right?
+                       // FIXME: is this really the same order that the docstring
+                       // from the CompletionList has?
+                       int c = s.compare(mids, Qt::CaseSensitive);
+                       if (c == 0) {
+                               l = mid;
+                               break;
+                       } else if (l == r) {
+                               l = n;
+                               break;
+                       } else if (c > 0)
+                               // middle is not far enough
+                               l = mid + 1;
+                       else
+                               // middle is too far
+                               r = mid - 1;
+               }
+
+               // loop was left without finding anything
+               if (r < l)
+                       i = n;
+               else
+                       i = l;
+               LASSERT(i <= n, /**/);
        }
 
        // select the first if none was found
        if (i == n)
                i = 0;
 
+       updateLock_++;
        popup()->setCurrentIndex(model.index(i, 0));
+       updateLock_--;
 }
 
 
-docstring GuiCompleter::longestUniqueCompletion() const {
+size_t commonPrefix(QString const & s1, QString const & s2)
+{
+       // find common prefix
+       size_t j;
+       size_t n1 = s1.length();
+       size_t n2 = s2.length();
+       for (j = 0; j < n1 && j < n2; ++j) {
+               if (s1.at(j) != s2.at(j))
+                       break;
+       }
+       return j;
+}
+
+
+docstring GuiCompleter::longestUniqueCompletion() const
+{
        QAbstractItemModel const & model = *popup()->model();
-       QString s = currentCompletion();
        size_t n = model.rowCount();
-
-       // iterate through the completions and cut off where s differs
-       for (size_t i = 0; i < n && s.length() > 0; ++i) {
-               QString const & is
-               = model.data(model.index(i, 0), Qt::EditRole).toString();
-
-               // find common prefix
-               size_t j;
-               size_t isn = is.length();
-               size_t sn = s.length();
-               for (j = 0; j < isn && j < sn; ++j) {
-                       if (s.at(j) != is.at(j))
-                               break;
+       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.
+               // Iterate through the completions and cut off where s differs
+               for (size_t i = 0; i < n && s.length() > 0; ++i) {
+                       QString const & is
+                       = model.data(model.index(i, 0), Qt::EditRole).toString();
+
+                       s = s.left(commonPrefix(is, s));
+               }
+       } else {
+               // For sorted models we can do binary search multiple times,
+               // each time to find the first string which has s not as prefix.
+               size_t i = 0;
+               while (i < n && s.length() > 0) {
+                       // find first string that does not have s as prefix
+                       // via binary search in [i,n-1]
+                       size_t r = n - 1;
+                       do {
+                               // get common prefix with the middle string
+                               size_t mid = (r + i) / 2;
+                               QString const & mids
+                               = model.data(model.index(mid, 0), 
+                                       Qt::EditRole).toString();
+                               size_t oldLen = s.length();
+                               size_t len = commonPrefix(mids, s);
+                               s = s.left(len);
+
+                               // left or right?
+                               if (oldLen == len) {
+                                       // middle is not far enough
+                                       i = mid + 1;
+                               } else {
+                                       // middle is maybe too far
+                                       r = mid;
+                               }
+                       } while (r - i > 0 && i < n);
                }
-               s = s.left(j);
        }
 
-       return from_utf8(fromqstr(s));
+       return qstring_to_ucs4(s);
 }
 
 
 void GuiCompleter::popupActivated(const QString & completion)
 {
-       Cursor & cur = gui_->bufferView().cursor();
+       Cursor cur = gui_->bufferView().cursor();
+       cur.updateFlags(Update::None);
+       
        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());
 }
@@ -573,8 +872,15 @@ void GuiCompleter::popupActivated(const QString & completion)
 
 void GuiCompleter::popupHighlighted(const QString & completion)
 {
+       if (updateLock_ > 0)
+               return;
+
        Cursor cur = gui_->bufferView().cursor();
-       updateInline(cur, completion);
+       cur.updateFlags(Update::None);
+       
+       if (inlineVisible())
+               updateInline(cur, completion);
+       
        if (cur.disp_.update())
                gui_->bufferView().processUpdateFlags(cur.disp_.update());
 }