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