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