]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiCompleter.cpp
* completion cursor
[lyx.git] / src / frontends / qt4 / GuiCompleter.cpp
index b50b472f3466e3058131648c128f49070e7044e4..1eafa25fa4b6688e72f24837fd7d0fbf45ca2b5a 100644 (file)
@@ -130,9 +130,10 @@ public:
                if (role != Qt::DisplayRole && role != Qt::EditRole)
                    return QVariant();
                    
-               if (index.column() == 0)
-                       return toqstr(list_->data(index.row()));
-               else if (index.column() == 1) {
+               if (index.column() == 0) {
+                       docstring const word = list_->data(index.row());
+                       return toqstr(word);
+               } else if (index.column() == 1) {
                        // get icon from cache
                        QPixmap scaled;
                        QString const name = ":" + toqstr(list_->icon(index.row()));
@@ -141,8 +142,7 @@ public:
                                QPixmap p = QPixmap(name);
                                if (!p.isNull()) {
                                        // scale it to 16x16 or smaller
-                                       scaled
-                                       = p.scaled(min(16, p.width()), min(16, p.height()), 
+                                       scaled = p.scaled(min(16, p.width()), min(16, p.height()), 
                                                Qt::KeepAspectRatio, Qt::SmoothTransformation);
                                }
 
@@ -176,6 +176,7 @@ 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(this));
        rtlItemDelegate_ = new RtlItemDelegate(this);
@@ -233,6 +234,12 @@ bool GuiCompleter::inlinePossible(Cursor const & cur) const
 }
 
 
+bool GuiCompleter::completionAvailable() const
+{
+       return popup()->model()->rowCount() > 0;
+}
+
+
 bool GuiCompleter::popupVisible() const
 {
        return popup()->isVisible();
@@ -262,28 +269,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
@@ -328,7 +319,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);
@@ -394,9 +385,10 @@ void GuiCompleter::updatePopup(Cursor & cur)
                rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
        
        // show/update popup
-       complete(rect);
        QTreeView * p = static_cast<QTreeView *>(popup());
        p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width());
+
+       complete(rect);
 }
 
 
@@ -422,14 +414,18 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate
        popup()->setItemDelegateForColumn(0, rtl ? rtlItemDelegate_ : 0);
 
        // set new model
-       Inset::CompletionList const * list
-       = cur.inset().createCompletionList(cur);
+       Inset::CompletionList const * list = cur.inset().createCompletionList(cur);
        setModel(new GuiCompletionModel(this, list));
        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);
@@ -444,7 +440,7 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate
                last_selection_ = s;
        else
                last_selection_ = old;
-       
+
        // show inline completion
        if (inlineUpdate)
                updateInline(cur, currentCompletion());
@@ -457,9 +453,19 @@ void GuiCompleter::showPopup(Cursor & cur)
                return;
        
        updateModel(cur, true, inlineVisible());
-       updatePrefix(cur);
 }
+
+
+void GuiCompleter::hidePopup(Cursor & cur)
+{
+       popup()->hide();
+       if (popup_timer_.isActive())
+               popup_timer_.stop();
        
+       if (!inlineVisible())
+               setModel(new GuiCompletionModel(this, 0));
+}
+
 
 void GuiCompleter::showInline(Cursor & cur)
 {
@@ -467,7 +473,16 @@ 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 (!popupVisible())
+               setModel(new GuiCompletionModel(this, 0));
 }
 
 
@@ -497,15 +512,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);
+       popupActivated(currentCompletion());
 }
 
 
@@ -546,9 +584,8 @@ void GuiCompleter::tab()
                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;
        }
@@ -604,14 +641,50 @@ void GuiCompleter::setCurrentCompletion(QString const & s)
                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;
+               BOOST_ASSERT(0 <= i && i <= n);
        }
 
        // select the first if none was found
@@ -624,25 +697,63 @@ void GuiCompleter::setCurrentCompletion(QString const & s)
 }
 
 
-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 (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));
@@ -657,7 +768,8 @@ void GuiCompleter::popupActivated(const QString & completion)
        docstring prefix = cur.inset().completionPrefix(cur);
        docstring postfix = from_utf8(fromqstr(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());
@@ -672,7 +784,8 @@ void GuiCompleter::popupHighlighted(const QString & completion)
        Cursor cur = gui_->bufferView().cursor();
        cur.updateFlags(Update::None);
        
-       updateInline(cur, completion);
+       if (inlineVisible())
+               updateInline(cur, completion);
        
        if (cur.disp_.update())
                gui_->bufferView().processUpdateFlags(cur.disp_.update());