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