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