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