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