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