]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiCompleter.cpp
Rename frontend qt4 to qt
[lyx.git] / src / frontends / qt / GuiCompleter.cpp
1 /**
2  * \file GuiCompleter.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Stefan Schimanski
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "GuiCompleter.h"
14
15 #include "Buffer.h"
16 #include "BufferView.h"
17 #include "CompletionList.h"
18 #include "Cursor.h"
19 #include "Dimension.h"
20 #include "GuiWorkArea.h"
21 #include "GuiView.h"
22 #include "LyX.h"
23 #include "LyXRC.h"
24 #include "Paragraph.h"
25 #include "qt_helpers.h"
26 #include "version.h"
27
28 #include "support/lassert.h"
29 #include "support/lstrings.h"
30 #include "support/debug.h"
31
32 #include <QApplication>
33 #include <QHeaderView>
34 #include <QKeyEvent>
35 #include <QPainter>
36 #include <QPixmapCache>
37 #include <QScrollBar>
38 #include <QItemDelegate>
39 #include <QTreeView>
40 #include <QTimer>
41
42 using namespace std;
43 using namespace lyx::support;
44
45 namespace lyx {
46 namespace frontend {
47
48 class CompleterItemDelegate : public QItemDelegate
49 {
50 public:
51         explicit CompleterItemDelegate(QObject * parent)
52                 : QItemDelegate(parent)
53         {}
54
55         ~CompleterItemDelegate()
56         {}
57
58 protected:
59         void paint(QPainter *painter, const QStyleOptionViewItem &option,
60                    const QModelIndex &index) const
61         {
62                 if (index.column() == 0) {
63                         QItemDelegate::paint(painter, option, index);
64                         return;
65                 }
66                 QStyleOptionViewItem opt = setOptions(index, option);
67                 QVariant value = index.data(Qt::DisplayRole);
68                 QPixmap pixmap = qvariant_cast<QPixmap>(value);
69
70                 // draw
71                 painter->save();
72                 drawBackground(painter, opt, index);
73                 if (!pixmap.isNull()) {
74                         const QSize size = pixmap.size();
75                         painter->drawPixmap(option.rect.left() + (16 - size.width()) / 2,
76                                 option.rect.top() + (option.rect.height() - size.height()) / 2,
77                                 pixmap);
78                 }
79                 drawFocus(painter, opt, option.rect);
80                 painter->restore();
81         }
82 };
83
84 class GuiCompletionModel : public QAbstractListModel
85 {
86 public:
87         ///
88         GuiCompletionModel(QObject * parent, CompletionList const * l)
89                 : QAbstractListModel(parent), list_(l)
90         {}
91         ///
92         ~GuiCompletionModel() { delete list_; }
93         ///
94         void setList(CompletionList const * l) {
95                 beginResetModel();
96                 delete list_;
97                 list_ = l;
98                 endResetModel();
99         }
100         ///
101         bool sorted() const
102         {
103                 if (list_)
104                         return list_->sorted();
105                 return false;
106         }
107         ///
108         int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
109         {
110                 return 2;
111         }
112         ///
113         int rowCount(const QModelIndex & /*parent*/ = QModelIndex()) const
114         {
115                 if (list_ == 0)
116                         return 0;
117                 return list_->size();
118         }
119
120         ///
121         QVariant data(const QModelIndex & index, int role) const
122         {
123                 if (list_ == 0)
124                         return QVariant();
125
126                 if (index.row() < 0 || index.row() >= rowCount())
127                         return QVariant();
128
129                 if (role != Qt::DisplayRole && role != Qt::EditRole)
130                     return QVariant();
131
132                 if (index.column() == 0)
133                         return toqstr(list_->data(index.row()));
134
135                 if (index.column() != 1)
136                         return QVariant();
137
138                 // get icon from cache
139                 QPixmap scaled;
140                 QString const name = ":" + toqstr(list_->icon(index.row()));
141                 if (name == ":")
142                         return scaled;
143                 if (!QPixmapCache::find("completion" + name, scaled)) {
144                         // load icon from disk
145                         QPixmap p = QPixmap(name);
146                         if (!p.isNull()) {
147                                 // scale it to 16x16 or smaller
148                                 scaled = p.scaled(min(16, p.width()), min(16, p.height()),
149                                         Qt::KeepAspectRatio, Qt::SmoothTransformation);
150                         }
151                         QPixmapCache::insert("completion" + name, scaled);
152                 }
153                 return scaled;
154         }
155
156 private:
157         /// owned by us
158         CompletionList const * list_;
159 };
160
161
162 GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
163         : QCompleter(parent), gui_(gui), old_cursor_(0), updateLock_(0),
164           inlineVisible_(false), popupVisible_(false),
165           modelActive_(false)
166 {
167         // Setup the completion popup
168         model_ = new GuiCompletionModel(this, 0);
169         setModel(model_);
170         setCompletionMode(QCompleter::PopupCompletion);
171         setCaseSensitivity(Qt::CaseSensitive);
172         setWidget(gui_);
173
174         // create the popup
175         QTreeView *listView = new QTreeView;
176         listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
177         listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
178         listView->setSelectionBehavior(QAbstractItemView::SelectRows);
179         listView->setSelectionMode(QAbstractItemView::SingleSelection);
180         listView->header()->hide();
181         listView->setIndentation(0);
182         listView->setUniformRowHeights(true);
183         setPopup(listView);
184
185         itemDelegate_ = new CompleterItemDelegate(this);
186         popup()->setItemDelegate(itemDelegate_);
187
188         // create timeout timers
189         popup_timer_.setSingleShot(true);
190         inline_timer_.setSingleShot(true);
191         connect(this, SIGNAL(highlighted(const QString &)),
192                 this, SLOT(popupHighlighted(const QString &)));
193         connect(this, SIGNAL(activated(const QString &)),
194                 this, SLOT(popupActivated(const QString &)));
195         connect(&popup_timer_, SIGNAL(timeout()),
196                 this, SLOT(showPopup()));
197         connect(&inline_timer_, SIGNAL(timeout()),
198                 this, SLOT(showInline()));
199 }
200
201
202 GuiCompleter::~GuiCompleter()
203 {
204         popup()->hide();
205 }
206
207
208 bool GuiCompleter::eventFilter(QObject * watched, QEvent * e)
209 {
210         // hijack back the tab key from the popup
211         // (which stole it from the workspace before)
212         if (e->type() == QEvent::KeyPress && popupVisible()) {
213                 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
214                 switch (ke->key()) {
215                 case Qt::Key_Tab:
216                         tab();
217                         ke->accept();
218                         return true;
219                 case Qt::Key_Escape:
220                         hidePopup();
221                         hideInline();
222                         updateVisibility(false, false);
223                         return true;
224                 default: break;
225                 }
226         }
227
228         return QCompleter::eventFilter(watched, e);
229 }
230
231
232 bool GuiCompleter::popupPossible(Cursor const & cur) const
233 {
234         return QApplication::activeWindow()
235                 && gui_->hasFocus()
236                 && cur.inset().completionSupported(cur);
237 }
238
239
240 bool GuiCompleter::inlinePossible(Cursor const & cur) const
241 {
242         return cur.inset().inlineCompletionSupported(cur);
243 }
244
245
246 bool GuiCompleter::uniqueCompletionAvailable() const
247 {
248         if (!modelActive_)
249                 return false;
250
251         size_t n = popup()->model()->rowCount();
252         if (n > 1 || n == 0)
253                 return false;
254
255         // if there is exactly one, we have to check whether it is a
256         // real completion, i.e. longer than the current prefix.
257         if (completionPrefix() == currentCompletion())
258                 return false;
259
260         return true;
261 }
262
263
264 bool GuiCompleter::completionAvailable() const
265 {
266         if (!modelActive_)
267                 return false;
268
269         size_t n = popup()->model()->rowCount();
270
271         // if there is exactly one, we have to check whether it is a
272         // real completion, i.e. longer than the current prefix.
273         if (n == 1 && completionPrefix() == currentCompletion())
274             return false;
275
276         return n > 0;
277 }
278
279
280 bool GuiCompleter::popupVisible() const
281 {
282         return popupVisible_;
283 }
284
285
286 bool GuiCompleter::inlineVisible() const
287 {
288         // In fact using BufferView::inlineCompletionPos.empty() should be
289         // here. But unfortunately this information is not good enough
290         // because destructive operations like backspace might invalidate
291         // inlineCompletionPos. But then the completion should stay visible
292         // (i.e. reshown on the next update). Hence be keep this information
293         // in the inlineVisible_ variable.
294         return inlineVisible_;
295 }
296
297
298 void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep)
299 {
300         // parameters which affect the completion
301         bool moved = cur != old_cursor_;
302         if (moved)
303                 old_cursor_ = cur;
304
305         bool const possiblePopupState = popupPossible(cur);
306         bool const possibleInlineState = inlinePossible(cur);
307
308         // we moved or popup state is not ok for popup?
309         if ((moved && !keep) || !possiblePopupState)
310                 hidePopup();
311
312         // we moved or inline state is not ok for inline completion?
313         if ((moved && !keep) || !possibleInlineState)
314                 hideInline(cur);
315
316         // we inserted something and are in a possible popup state?
317         if (!popupVisible() && possiblePopupState && start
318                 && cur.inset().automaticPopupCompletion())
319                 popup_timer_.start(int(lyxrc.completion_popup_delay * 1000));
320
321         // we inserted something and are in a possible inline completion state?
322         if (!inlineVisible() && possibleInlineState && start
323                 && cur.inset().automaticInlineCompletion())
324                 inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
325         else if (cur.inMathed() && !lyxrc.completion_inline_math) {
326                 // no inline completion, hence a metrics update is needed
327                 if (!(cur.result().screenUpdate() & Update::Force))
328                         cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
329         }
330
331         // update prefix if any completion is possible
332         bool modelActive = modelActive_ && model()->rowCount() > 0;
333         if (possiblePopupState || possibleInlineState) {
334                 if (modelActive)
335                         updatePrefix(cur);
336                 else
337                         updateAvailability();
338         }
339 }
340
341
342 void GuiCompleter::updateVisibility(bool start, bool keep)
343 {
344         Cursor cur = gui_->bufferView().cursor();
345         cur.screenUpdateFlags(Update::None);
346
347         updateVisibility(cur, start, keep);
348
349         if (cur.result().screenUpdate())
350                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
351 }
352
353
354 void GuiCompleter::updatePrefix(Cursor const & cur)
355 {
356         // get new prefix. Do nothing if unchanged
357         QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
358         if (newPrefix == completionPrefix())
359                 return;
360
361         // value which should be kept selected
362         QString old = currentCompletion();
363         if (old.length() == 0)
364                 old = last_selection_;
365
366         // update completer to new prefix
367         setCompletionPrefix(newPrefix);
368
369         // update popup because its size might have changed
370         if (popupVisible())
371                 updatePopup(cur);
372
373         // restore old selection
374         setCurrentCompletion(old);
375
376         // if popup is not empty, the new selection will
377         // be our last valid one
378         QString const & s = currentCompletion();
379         if (popupVisible() || inlineVisible()) {
380                 if (s.length() > 0)
381                         last_selection_ = s;
382                 else
383                         last_selection_ = old;
384         }
385
386         // update inline completion because the default
387         // completion string might have changed
388         if (inlineVisible())
389                 updateInline(cur, s);
390 }
391
392
393 void GuiCompleter::updateInline(Cursor const & cur, QString const & completion)
394 {
395         if (!cur.inset().inlineCompletionSupported(cur))
396                 return;
397
398         // compute postfix
399         docstring prefix = cur.inset().completionPrefix(cur);
400         docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
401
402         // shorten it if necessary
403         if (lyxrc.completion_inline_dots != -1)
404                 support::truncateWithEllipsis(postfix,
405                                                                           unsigned(lyxrc.completion_inline_dots));
406
407         // set inline completion at cursor position
408         size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
409         gui_->bufferView().setInlineCompletion(cur, cur, postfix, uniqueTo - prefix.size());
410         inlineVisible_ = true;
411 }
412
413
414 void GuiCompleter::updatePopup(Cursor const & cur)
415 {
416         if (!cur.inset().completionSupported(cur))
417                 return;
418
419         popupVisible_ = true;
420
421         if (completionCount() == 0) {
422                 QTimer::singleShot(0, popup(), SLOT(hide()));
423                 return;
424         }
425
426         QTimer::singleShot(0, this, SLOT(asyncUpdatePopup()));
427 }
428
429
430 void GuiCompleter::asyncUpdatePopup()
431 {
432         Cursor cur = gui_->bufferView().cursor();
433         if (!cur.inset().completionSupported(cur)
434                   || !cur.bv().paragraphVisible(cur)) {
435                 popupVisible_ = false;
436                 return;
437         }
438
439         // get dimensions of completion prefix
440         Dimension dim;
441         int x;
442         int y;
443         cur.inset().completionPosAndDim(cur, x, y, dim);
444
445         // and calculate the rect of the popup
446         QRect rect;
447         if (popup()->layoutDirection() == Qt::RightToLeft)
448                 rect = QRect(x + dim.width() - 200, y - dim.ascent() - 3, 200, dim.height() + 6);
449         else
450                 rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
451
452         // Resize the columns in the popup.
453         // This should really be in the constructor. But somehow the treeview
454         // has a bad memory about it and we have to tell him again and again.
455         QTreeView * listView = static_cast<QTreeView *>(popup());
456         listView->header()->setStretchLastSection(false);
457         setSectionResizeMode(listView->header(), 0, QHeaderView::Stretch);
458         setSectionResizeMode(listView->header(), 1, QHeaderView::Fixed);
459         listView->header()->resizeSection(1, 22);
460
461         // show/update popup
462         complete(rect);
463 }
464
465
466 void GuiCompleter::updateAvailability()
467 {
468         // this should really only be of interest if no completion is
469         // visible yet, i.e. especially if automatic completion is disabled.
470         if (inlineVisible() || popupVisible())
471                 return;
472         Cursor const & cur = gui_->bufferView().cursor();
473         if (!popupPossible(cur) && !inlinePossible(cur))
474                 return;
475
476         updateModel(cur, false, false);
477 }
478
479
480 void GuiCompleter::updateModel(Cursor const & cur, bool popupUpdate, bool inlineUpdate)
481 {
482         // value which should be kept selected
483         QString old = currentCompletion();
484         if (old.length() == 0)
485                 old = last_selection_;
486
487         // set whether rtl
488         bool rtl = false;
489         if (cur.inTexted()) {
490                 Paragraph const & par = cur.paragraph();
491                 Font const & font =
492                         par.getFontSettings(cur.bv().buffer().params(), cur.pos());
493                 rtl = font.isVisibleRightToLeft();
494         }
495         popup()->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight);
496
497         // set new model
498         CompletionList const * list = cur.inset().createCompletionList(cur);
499         model_->setList(list);
500         modelActive_ = true;
501         if (list->sorted())
502                 setModelSorting(QCompleter::CaseSensitivelySortedModel);
503         else
504                 setModelSorting(QCompleter::UnsortedModel);
505
506         // set prefix
507         QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
508         if (newPrefix != completionPrefix())
509                 setCompletionPrefix(newPrefix);
510
511         // show popup
512         if (popupUpdate)
513                 updatePopup(cur);
514
515         // restore old selection
516         setCurrentCompletion(old);
517
518         // if popup is not empty, the new selection will
519         // be our last valid one
520         if (popupVisible() || inlineVisible()) {
521                 QString const & s = currentCompletion();
522                 if (s.length() > 0)
523                         last_selection_ = s;
524                 else
525                         last_selection_ = old;
526         }
527
528         // show inline completion
529         if (inlineUpdate)
530                 updateInline(cur, currentCompletion());
531 }
532
533
534 void GuiCompleter::showPopup(Cursor const & cur)
535 {
536         if (!popupPossible(cur))
537                 return;
538
539         updateModel(cur, true, inlineVisible());
540 }
541
542
543 void GuiCompleter::asyncHidePopup()
544 {
545         popup()->hide();
546         // do not clear model if it has been set by an event before the
547         // timeout got triggered.
548         if (!modelActive_ && !inlineVisible())
549                 model_->setList(0);
550 }
551
552
553 void GuiCompleter::showInline(Cursor const & cur)
554 {
555         if (!inlinePossible(cur))
556                 return;
557
558         updateModel(cur, popupVisible(), true);
559 }
560
561
562 void GuiCompleter::hideInline(Cursor const & cur)
563 {
564         gui_->bufferView().setInlineCompletion(cur, DocIterator(cur.buffer()), docstring());
565         inlineVisible_ = false;
566
567         if (inline_timer_.isActive())
568                 inline_timer_.stop();
569
570         // Trigger asynchronous part of hideInline. We might be
571         // in a dispatcher here and the setModel call might
572         // trigger focus events which is are not healthy here.
573         QTimer::singleShot(0, this, SLOT(asyncHideInline()));
574
575         // mark that the asynchronous part will reset the model
576         if (!popupVisible())
577                 modelActive_ = false;
578 }
579
580
581 void GuiCompleter::asyncHideInline()
582 {
583         // do not clear model if it has been set by an event before the
584         // timeout got triggered.
585         if (!modelActive_ && !popupVisible())
586                 model_->setList(0);
587 }
588
589
590 void GuiCompleter::showPopup()
591 {
592         Cursor cur = gui_->bufferView().cursor();
593         cur.screenUpdateFlags(Update::None);
594
595         showPopup(cur);
596
597         // redraw if needed
598         if (cur.result().screenUpdate())
599                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
600 }
601
602
603 void GuiCompleter::showInline()
604 {
605         Cursor cur = gui_->bufferView().cursor();
606         cur.screenUpdateFlags(Update::None);
607
608         showInline(cur);
609
610         // redraw if needed
611         if (cur.result().screenUpdate())
612                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
613 }
614
615
616 void GuiCompleter::hidePopup()
617 {
618         popupVisible_ = false;
619
620         if (popup_timer_.isActive())
621                 popup_timer_.stop();
622
623         // hide popup asynchronously because we might be here inside of
624         // LFUN dispatchers. Hiding a popup can trigger a focus event on the
625         // workarea which then redisplays the cursor. But the metrics are not
626         // yet up to date such that the coord cache has not all insets yet. The
627         // cursorPos methods would triggers asserts in the coord cache then.
628         QTimer::singleShot(0, this, SLOT(asyncHidePopup()));
629
630         // mark that the asynchronous part will reset the model
631         if (!inlineVisible())
632                 modelActive_ = false;
633 }
634
635
636 void GuiCompleter::hideInline()
637 {
638         Cursor cur = gui_->bufferView().cursor();
639         cur.screenUpdateFlags(Update::None);
640
641         hideInline(cur);
642
643         // redraw if needed
644         if (cur.result().screenUpdate())
645                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
646 }
647
648
649 void GuiCompleter::activate()
650 {
651         if (!popupVisible() && !inlineVisible())
652                 tab();
653         else
654                 popupActivated(currentCompletion());
655 }
656
657
658 void GuiCompleter::tab()
659 {
660         BufferView * bv = &gui_->bufferView();
661         Cursor cur = bv->cursor();
662         cur.screenUpdateFlags(Update::None);
663
664         // check that inline completion is active
665         if (!inlineVisible() && !uniqueCompletionAvailable()) {
666                 // try to activate the inline completion
667                 if (cur.inset().inlineCompletionSupported(cur)) {
668                         showInline();
669
670                         // show popup without delay because the completion was not unique
671                         if (lyxrc.completion_popup_after_complete
672                             && !popupVisible()
673                             && popup()->model()->rowCount() > 1)
674                                 popup_timer_.start(0);
675
676                         return;
677                 }
678                 // or try popup
679                 if (!popupVisible() && cur.inset().completionSupported(cur)) {
680                         showPopup();
681                         return;
682                 }
683
684                 return;
685         }
686
687         // Make undo possible
688         UndoGroupHelper ugh;
689         cur.recordUndo();
690
691         // If completion is active, at least complete by one character
692         docstring prefix = cur.inset().completionPrefix(cur);
693         docstring completion = qstring_to_ucs4(currentCompletion());
694         if (completion.size() <= prefix.size()) {
695                 // finalize completion
696                 cur.inset().insertCompletion(cur, docstring(), true);
697
698                 // hide popup and inline completion
699                 hidePopup();
700                 hideInline(cur);
701                 updateVisibility(false, false);
702                 return;
703         }
704         docstring nextchar = completion.substr(prefix.size(), 1);
705         if (!cur.inset().insertCompletion(cur, nextchar, false))
706                 return;
707         updatePrefix(cur);
708
709         // try to complete as far as it is unique
710         docstring longestCompletion = longestUniqueCompletion();
711         prefix = cur.inset().completionPrefix(cur);
712         docstring postfix = longestCompletion.substr(min(longestCompletion.size(), prefix.size()));
713         cur.inset().insertCompletion(cur, postfix, false);
714         old_cursor_ = bv->cursor();
715         updatePrefix(cur);
716
717         // show popup without delay because the completion was not unique
718         if (lyxrc.completion_popup_after_complete
719             && !popupVisible()
720             && popup()->model()->rowCount() > 1)
721                 popup_timer_.start(0);
722
723         // redraw if needed
724         if (cur.result().screenUpdate())
725                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
726 }
727
728
729 QString GuiCompleter::currentCompletion() const
730 {
731         if (!popup()->selectionModel()->hasSelection())
732                 return QString();
733
734         // Not sure if this is bug in Qt: currentIndex() always
735         // return the first element in the list.
736         QModelIndex idx = popup()->currentIndex();
737         return popup()->model()->data(idx, Qt::EditRole).toString();
738 }
739
740
741 void GuiCompleter::setCurrentCompletion(QString const & s)
742 {
743         QAbstractItemModel const & model = *popup()->model();
744         size_t n = model.rowCount();
745         if (n == 0)
746                 return;
747
748         // select the first if s is empty
749         if (s.length() == 0) {
750                 updateLock_++;
751                 popup()->setCurrentIndex(model.index(0, 0));
752                 updateLock_--;
753                 return;
754         }
755
756         // find old selection in model
757         size_t i;
758         if (modelSorting() == QCompleter::UnsortedModel) {
759                 // In unsorted models, iterate through list until the s is found
760                 for (i = 0; i < n; ++i) {
761                         QString const & is
762                         = model.data(model.index(i, 0), Qt::EditRole).toString();
763                         if (is == s)
764                                 break;
765                 }
766         } else {
767                 // In sorted models, do binary search for s.
768                 int l = 0;
769                 int r = n - 1;
770                 while (r >= l && l < int(n)) {
771                         size_t mid = (r + l) / 2;
772                         QString const & mids
773                         = model.data(model.index(mid, 0),
774                                      Qt::EditRole).toString();
775
776                         // left or right?
777                         // FIXME: is this really the same order that the docstring
778                         // from the CompletionList has?
779                         int c = s.compare(mids, Qt::CaseSensitive);
780                         if (c == 0) {
781                                 l = mid;
782                                 break;
783                         } else if (l == r) {
784                                 l = n;
785                                 break;
786                         } else if (c > 0)
787                                 // middle is not far enough
788                                 l = mid + 1;
789                         else
790                                 // middle is too far
791                                 r = mid - 1;
792                 }
793
794                 // loop was left without finding anything
795                 if (r < l)
796                         i = n;
797                 else
798                         i = l;
799                 // we can try to recover
800                 LASSERT(i <= n, i = 0);
801         }
802
803         // select the first if none was found
804         if (i == n)
805                 i = 0;
806
807         updateLock_++;
808         popup()->setCurrentIndex(model.index(i, 0));
809         updateLock_--;
810 }
811
812
813 size_t commonPrefix(QString const & s1, QString const & s2)
814 {
815         // find common prefix
816         size_t j;
817         size_t n1 = s1.length();
818         size_t n2 = s2.length();
819         for (j = 0; j < n1 && j < n2; ++j) {
820                 if (s1.at(j) != s2.at(j))
821                         break;
822         }
823         return j;
824 }
825
826
827 docstring GuiCompleter::longestUniqueCompletion() const
828 {
829         QAbstractItemModel const & model = *popup()->model();
830         size_t n = model.rowCount();
831         if (n == 0)
832                 return docstring();
833         QString s = model.data(model.index(0, 0), Qt::EditRole).toString();
834
835         if (modelSorting() == QCompleter::UnsortedModel) {
836                 // For unsorted model we cannot do more than iteration.
837                 // Iterate through the completions and cut off where s differs
838                 for (size_t i = 0; i < n && s.length() > 0; ++i) {
839                         QString const & is
840                         = model.data(model.index(i, 0), Qt::EditRole).toString();
841
842                         s = s.left(commonPrefix(is, s));
843                 }
844         } else {
845                 // For sorted models we can do binary search multiple times,
846                 // each time to find the first string which has s not as prefix.
847                 size_t i = 0;
848                 while (i < n && s.length() > 0) {
849                         // find first string that does not have s as prefix
850                         // via binary search in [i,n-1]
851                         size_t r = n - 1;
852                         do {
853                                 // get common prefix with the middle string
854                                 size_t mid = (r + i) / 2;
855                                 QString const & mids
856                                 = model.data(model.index(mid, 0),
857                                         Qt::EditRole).toString();
858                                 size_t oldLen = s.length();
859                                 size_t len = commonPrefix(mids, s);
860                                 s = s.left(len);
861
862                                 // left or right?
863                                 if (oldLen == len) {
864                                         // middle is not far enough
865                                         i = mid + 1;
866                                 } else {
867                                         // middle is maybe too far
868                                         r = mid;
869                                 }
870                         } while (r - i > 0 && i < n);
871                 }
872         }
873
874         return qstring_to_ucs4(s);
875 }
876
877
878 void GuiCompleter::popupActivated(const QString & completion)
879 {
880         Cursor cur = gui_->bufferView().cursor();
881         cur.screenUpdateFlags(Update::None);
882
883         cur.beginUndoGroup();
884         cur.recordUndo();
885
886         docstring prefix = cur.inset().completionPrefix(cur);
887         docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
888         cur.inset().insertCompletion(cur, postfix, true);
889         hidePopup();
890         hideInline(cur);
891
892         if (cur.result().screenUpdate())
893                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
894         cur.endUndoGroup();
895 }
896
897
898 void GuiCompleter::popupHighlighted(const QString & completion)
899 {
900         if (updateLock_ > 0)
901                 return;
902
903         Cursor cur = gui_->bufferView().cursor();
904         cur.screenUpdateFlags(Update::None);
905
906         if (inlineVisible())
907                 updateInline(cur, completion);
908
909         if (cur.result().screenUpdate())
910                 gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
911 }
912
913 } // namespace frontend
914 } // namespace lyx
915
916 #include "moc_GuiCompleter.cpp"