]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiCompleter.cpp
b9f8ab4c4e2028a1e0e7e7d8181cedb58dbc3e18
[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 "CompletionList.h"
18 #include "Cursor.h"
19 #include "Dimension.h"
20 #include "FuncRequest.h"
21 #include "GuiView.h"
22 #include "LyXFunc.h"
23 #include "LyXRC.h"
24 #include "Paragraph.h"
25 #include "version.h"
26
27 #include "support/debug.h"
28
29 #include <QApplication>
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, CompletionList const * l)
107                 : QAbstractListModel(parent), list_(l) {}
108         ///
109         ~GuiCompletionModel()
110                 { delete list_; }
111         ///
112         bool sorted() const
113         {
114                 if (list_)
115                         return list_->sorted();
116                 else
117                         return false;
118         }
119         ///
120         int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
121         {
122                 return 2;
123         }
124         ///
125         int rowCount(const QModelIndex & /*parent*/ = QModelIndex()) const
126         {
127                 if (list_ == 0)
128                         return 0;
129                 else
130                         return list_->size();
131         }
132
133         ///
134         QVariant data(const QModelIndex & index, int role) const
135         {
136                 if (list_ == 0)
137                         return QVariant();
138
139                 if (index.row() < 0 || index.row() >= rowCount())
140                         return QVariant();
141
142                 if (role != Qt::DisplayRole && role != Qt::EditRole)
143                     return QVariant();
144                     
145                 if (index.column() == 0)
146                         return toqstr(list_->data(index.row()));
147                 else if (index.column() == 1) {
148                         // get icon from cache
149                         QPixmap scaled;
150                         QString const name = ":" + toqstr(list_->icon(index.row()));
151                         if (!QPixmapCache::find("completion" + name, scaled)) {
152                                 // load icon from disk
153                                 QPixmap p = QPixmap(name);
154                                 if (!p.isNull()) {
155                                         // scale it to 16x16 or smaller
156                                         scaled = p.scaled(min(16, p.width()), min(16, p.height()), 
157                                                 Qt::KeepAspectRatio, Qt::SmoothTransformation);
158                                 }
159
160                                 QPixmapCache::insert("completion" + name, scaled);
161                         }
162                         return scaled;
163                 }
164                 return QVariant();
165         }
166
167 private:
168         /// owned by us
169         CompletionList const * list_;
170 };
171
172
173 GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
174         : QCompleter(parent), gui_(gui), updateLock_(0),
175           inlineVisible_(false), popupVisible_(false),
176           modelActive_(false)
177 {
178         // Setup the completion popup
179         setModel(new GuiCompletionModel(this, 0));
180         setCompletionMode(QCompleter::PopupCompletion);
181         setWidget(gui_);
182         
183         // create the popup
184         QTreeView *listView = new QTreeView;
185         listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
186         listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
187         listView->setSelectionBehavior(QAbstractItemView::SelectRows);
188         listView->setSelectionMode(QAbstractItemView::SingleSelection);
189         listView->header()->hide();
190         listView->setIndentation(0);
191         listView->setUniformRowHeights(true);
192         setPopup(listView);
193         
194         rtlItemDelegate_ = new RtlItemDelegate(this);
195         popup()->setItemDelegateForColumn(0, rtlItemDelegate_);
196         popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
197         
198         // create timeout timers
199         popup_timer_.setSingleShot(true);
200         inline_timer_.setSingleShot(true);
201         connect(this, SIGNAL(highlighted(const QString &)),
202                 this, SLOT(popupHighlighted(const QString &)));
203         connect(this, SIGNAL(activated(const QString &)),
204                 this, SLOT(popupActivated(const QString &)));
205         connect(&popup_timer_, SIGNAL(timeout()),
206                 this, SLOT(showPopup()));
207         connect(&inline_timer_, SIGNAL(timeout()),
208                 this, SLOT(showInline()));
209 }
210
211
212 GuiCompleter::~GuiCompleter()
213 {
214         popup()->hide();
215 }
216
217
218 bool GuiCompleter::eventFilter(QObject * watched, QEvent * e)
219 {
220         // hijack back the tab key from the popup
221         // (which stole it from the workspace before)
222         if (e->type() == QEvent::KeyPress && popupVisible()) {
223                 QKeyEvent *ke = static_cast<QKeyEvent *>(e);
224                 switch (ke->key()) {
225                 case Qt::Key_Tab:
226                         tab();
227                         ke->accept();
228                         return true;
229                 default: break;
230                 }
231         }
232         
233         return QCompleter::eventFilter(watched, e);
234 }
235
236
237 bool GuiCompleter::popupPossible(Cursor const & cur) const
238 {
239         return QApplication::activeWindow()
240                 && gui_->hasFocus()
241                 && cur.inset().completionSupported(cur);
242 }
243
244
245 bool GuiCompleter::inlinePossible(Cursor const & cur) const
246 {
247         return cur.inset().inlineCompletionSupported(cur);
248 }
249
250
251 bool GuiCompleter::completionAvailable() const
252 {
253         if (!modelActive_)
254                 return false;
255
256         size_t n = popup()->model()->rowCount();
257
258         // if there is exactly one, we have to check whether it is a 
259         // real completion, i.e. longer than the current prefix.
260         if (n == 1 && completionPrefix() == currentCompletion())
261             return false;
262
263         return n > 0;
264 }
265
266
267 bool GuiCompleter::popupVisible() const
268 {
269         return popupVisible_;
270 }
271
272
273 bool GuiCompleter::inlineVisible() const
274 {
275         // In fact using BufferView::inlineCompletionPos.empty() should be
276         // here. But unfortunately this information is not good enough
277         // because destructive operations like backspace might invalidate
278         // inlineCompletionPos. But then the completion should stay visible
279         // (i.e. reshown on the next update). Hence be keep this information
280         // in the inlineVisible_ variable.
281         return inlineVisible_;
282 }
283
284
285 void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cursorInView)
286 {
287         // parameters which affect the completion
288         bool moved = cur != old_cursor_;
289         if (moved)
290                 old_cursor_ = cur;
291
292         bool possiblePopupState = popupPossible(cur) && cursorInView;
293         bool possibleInlineState = inlinePossible(cur) && cursorInView;
294
295         // we moved or popup state is not ok for popup?
296         if ((moved && !keep) || !possiblePopupState)
297                 hidePopup(cur);
298
299         // we moved or inline state is not ok for inline completion?
300         if ((moved && !keep) || !possibleInlineState)
301                 hideInline(cur);
302
303         // we inserted something and are in a possible popup state?
304         if (!popupVisible() && possiblePopupState && start
305                 && cur.inset().automaticPopupCompletion())
306                 popup_timer_.start(int(lyxrc.completion_popup_delay * 1000));
307
308         // we inserted something and are in a possible inline completion state?
309         if (!inlineVisible() && possibleInlineState && start
310                 && cur.inset().automaticInlineCompletion())
311                 inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
312
313         // update prefix if any completion is possible
314         bool modelActive = modelActive_ && model()->rowCount() > 0;
315         if (possiblePopupState || possibleInlineState) {
316                 if (modelActive)
317                         updatePrefix(cur);
318                 else
319                         updateAvailability();
320         }
321 }
322
323
324 void GuiCompleter::updateVisibility(bool start, bool keep)
325 {
326         Cursor cur = gui_->bufferView().cursor();
327         cur.updateFlags(Update::None);
328         
329         updateVisibility(cur, start, keep);
330         
331         if (cur.disp_.update())
332                 gui_->bufferView().processUpdateFlags(cur.disp_.update());
333 }
334
335
336 void GuiCompleter::updatePrefix(Cursor & cur)
337 {
338         // get new prefix. Do nothing if unchanged
339         QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
340         if (newPrefix == completionPrefix())
341                 return;
342         
343         // value which should be kept selected
344         QString old = currentCompletion();
345         if (old.length() == 0)
346                 old = last_selection_;
347         
348         // update completer to new prefix
349         setCompletionPrefix(newPrefix);
350
351         // update popup because its size might have changed
352         if (popupVisible())
353                 updatePopup(cur);
354
355         // restore old selection
356         setCurrentCompletion(old);
357         
358         // if popup is not empty, the new selection will
359         // be our last valid one
360         QString const & s = currentCompletion();
361         if (popupVisible() || inlineVisible()) {
362                 if (s.length() > 0)
363                         last_selection_ = s;
364                 else
365                         last_selection_ = old;
366         }
367
368         // update inline completion because the default
369         // completion string might have changed
370         if (inlineVisible())
371                 updateInline(cur, s);
372 }
373
374
375 void GuiCompleter::updateInline(Cursor & cur, QString const & completion)
376 {
377         if (!cur.inset().inlineCompletionSupported(cur))
378                 return;
379         
380         // compute postfix
381         docstring prefix = cur.inset().completionPrefix(cur);
382         docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
383         
384         // shorten it if necessary
385         if (lyxrc.completion_inline_dots != -1
386             && postfix.size() > unsigned(lyxrc.completion_inline_dots))
387                 postfix = postfix.substr(0, lyxrc.completion_inline_dots - 1) + "...";
388
389         // set inline completion at cursor position
390         size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
391         gui_->bufferView().setInlineCompletion(cur, cur, postfix, uniqueTo - prefix.size());
392         inlineVisible_ = true;
393 }
394
395
396 void GuiCompleter::updatePopup(Cursor & cur)
397 {
398         if (!cur.inset().completionSupported(cur))
399                 return;
400         
401         popupVisible_ = true;
402
403         if (completionCount() == 0) {
404                 QTimer::singleShot(0, popup(), SLOT(hide()));
405                 return;
406         }
407
408         QTimer::singleShot(0, this, SLOT(asyncUpdatePopup()));
409 }
410
411
412 void GuiCompleter::asyncUpdatePopup()
413 {
414         Cursor cur = gui_->bufferView().cursor();
415         if (!cur.inset().completionSupported(cur)) {
416                 popupVisible_ = false;
417                 return;
418         }
419
420         // get dimensions of completion prefix
421         Dimension dim;
422         int x;
423         int y;
424         cur.inset().completionPosAndDim(cur, x, y, dim);
425         
426         // and calculate the rect of the popup
427         QRect rect;
428         if (popup()->layoutDirection() == Qt::RightToLeft)
429                 rect = QRect(x + dim.width() - 200, y - dim.ascent() - 3, 200, dim.height() + 6);
430         else
431                 rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
432         
433         // Resize the columns in the popup.
434         // This should really be in the constructor. But somehow the treeview
435         // has a bad memory about it and we have to tell him again and again.
436         QTreeView * listView = static_cast<QTreeView *>(popup());
437         listView->header()->setStretchLastSection(false);
438         listView->header()->setResizeMode(0, QHeaderView::Stretch);
439         listView->header()->setResizeMode(1, QHeaderView::Fixed);
440         listView->header()->resizeSection(1, 22);
441         
442         // show/update popup
443         complete(rect);
444 }
445
446
447 void GuiCompleter::updateAvailability()
448 {
449         // this should really only be of interest if no completion is
450         // visible yet, i.e. especially if automatic completion is disabled.
451         if (inlineVisible() || popupVisible())
452                 return;
453         Cursor & cur = gui_->bufferView().cursor();
454         if (!popupPossible(cur) && !inlinePossible(cur))
455                 return;
456         
457         updateModel(cur, false, false);
458 }
459         
460
461 void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate)
462 {
463         // value which should be kept selected
464         QString old = currentCompletion();
465         if (old.length() == 0)
466                 old = last_selection_;
467
468         // set whether rtl
469         bool rtl = false;
470         if (cur.inTexted()) {
471                 Paragraph const & par = cur.paragraph();
472                 Font const font =
473                 par.getFontSettings(cur.bv().buffer().params(), cur.pos());
474                 rtl = font.isVisibleRightToLeft();
475         }
476         popup()->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight);
477
478         // turn the direction of the strings in the popup.
479         // Qt does not do that itself.
480         rtlItemDelegate_->setEnabled(rtl);
481
482         // set new model
483         CompletionList const * list = cur.inset().createCompletionList(cur);
484         setModel(new GuiCompletionModel(this, 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 & cur)
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                 setModel(new GuiCompletionModel(this, 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                 setModel(new GuiCompletionModel(this, 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                 return;
647
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()) {
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         // If completion is active, at least complete by one character
682         docstring prefix = cur.inset().completionPrefix(cur);
683         docstring completion = from_utf8(fromqstr(currentCompletion()));
684         if (completion.size() <= prefix.size()) {
685                 // finalize completion
686                 cur.inset().insertCompletion(cur, docstring(), true);
687                 
688                 // hide popup and inline completion
689                 hidePopup(cur);
690                 hideInline(cur);
691                 updateVisibility(false, false);
692                 return;
693         }
694         docstring nextchar = completion.substr(prefix.size(), 1);
695         if (!cur.inset().insertCompletion(cur, nextchar, false))
696                 return;
697         updatePrefix(cur);
698
699         // try to complete as far as it is unique
700         docstring longestCompletion = longestUniqueCompletion();
701         prefix = cur.inset().completionPrefix(cur);
702         docstring postfix = longestCompletion.substr(min(longestCompletion.size(), prefix.size()));
703         cur.inset().insertCompletion(cur, postfix, false);
704         old_cursor_ = bv->cursor();
705         updatePrefix(cur);
706
707         // show popup without delay because the completion was not unique
708         if (lyxrc.completion_popup_after_complete
709             && !popupVisible()
710             && popup()->model()->rowCount() > 1)
711                 popup_timer_.start(0);
712
713         // redraw if needed
714         if (cur.disp_.update())
715                 gui_->bufferView().processUpdateFlags(cur.disp_.update());
716 }
717
718
719 QString GuiCompleter::currentCompletion() const
720 {
721         if (!popup()->selectionModel()->hasSelection())
722                 return QString();
723
724         // Not sure if this is bug in Qt: currentIndex() always 
725         // return the first element in the list.
726         QModelIndex idx = popup()->currentIndex();
727         return popup()->model()->data(idx, Qt::EditRole).toString();
728 }
729
730
731 void GuiCompleter::setCurrentCompletion(QString const & s)
732 {       
733         QAbstractItemModel const & model = *popup()->model();
734         size_t n = model.rowCount();
735         if (n == 0)
736                 return;
737
738         // select the first if s is empty
739         if (s.length() == 0) {
740                 updateLock_++;
741                 popup()->setCurrentIndex(model.index(0, 0));
742                 updateLock_--;
743                 return;
744         }
745
746         // find old selection in model
747         size_t i;
748         if (modelSorting() == QCompleter::UnsortedModel) {
749                 // In unsorted models, iterate through list until the s is found
750                 for (i = 0; i < n; ++i) {
751                         QString const & is
752                         = model.data(model.index(i, 0), Qt::EditRole).toString();
753                         if (is == s)
754                                 break;
755                 }
756         } else {
757                 // In sorted models, do binary search for s.
758                 int l = 0;
759                 int r = n - 1;
760                 while (r >= l && l < int(n)) {
761                         size_t mid = (r + l) / 2;
762                         QString const & mids
763                         = model.data(model.index(mid, 0),
764                                      Qt::EditRole).toString();
765
766                         // left or right?
767                         // FIXME: is this really the same order that the docstring
768                         // from the CompletionList has?
769                         int c = s.compare(mids, Qt::CaseSensitive);
770                         if (c == 0) {
771                                 l = mid;
772                                 break;
773                         } else if (l == r) {
774                                 l = n;
775                                 break;
776                         } else if (c > 0)
777                                 // middle is not far enough
778                                 l = mid + 1;
779                         else
780                                 // middle is too far
781                                 r = mid - 1;
782                 }
783
784                 // loop was left without finding anything
785                 if (r < l)
786                         i = n;
787                 else
788                         i = l;
789                 BOOST_ASSERT(0 <= i && i <= n);
790         }
791
792         // select the first if none was found
793         if (i == n)
794                 i = 0;
795
796         updateLock_++;
797         popup()->setCurrentIndex(model.index(i, 0));
798         updateLock_--;
799 }
800
801
802 size_t commonPrefix(QString const & s1, QString const & s2)
803 {
804         // find common prefix
805         size_t j;
806         size_t n1 = s1.length();
807         size_t n2 = s2.length();
808         for (j = 0; j < n1 && j < n2; ++j) {
809                 if (s1.at(j) != s2.at(j))
810                         break;
811         }
812         return j;
813 }
814
815
816 docstring GuiCompleter::longestUniqueCompletion() const
817 {
818         QAbstractItemModel const & model = *popup()->model();
819         size_t n = model.rowCount();
820         if (n == 0)
821                 return docstring();
822         QString s = model.data(model.index(0, 0), Qt::EditRole).toString();
823         
824         if (modelSorting() == QCompleter::UnsortedModel) {
825                 // For unsorted model we cannot do more than iteration.
826                 // Iterate through the completions and cut off where s differs
827                 for (size_t i = 0; i < n && s.length() > 0; ++i) {
828                         QString const & is
829                         = model.data(model.index(i, 0), Qt::EditRole).toString();
830
831                         s = s.left(commonPrefix(is, s));
832                 }
833         } else {
834                 // For sorted models we can do binary search multiple times,
835                 // each time to find the first string which has s not as prefix.
836                 size_t i = 0;
837                 while (i < n && s.length() > 0) {
838                         // find first string that does not have s as prefix
839                         // via binary search in [i,n-1]
840                         size_t r = n - 1;
841                         do {
842                                 // get common prefix with the middle string
843                                 size_t mid = (r + i) / 2;
844                                 QString const & mids
845                                 = model.data(model.index(mid, 0), 
846                                         Qt::EditRole).toString();
847                                 size_t oldLen = s.length();
848                                 size_t len = commonPrefix(mids, s);
849                                 s = s.left(len);
850
851                                 // left or right?
852                                 if (oldLen == len) {
853                                         // middle is not far enough
854                                         i = mid + 1;
855                                 } else {
856                                         // middle is maybe too far
857                                         r = mid;
858                                 }
859                         } while (r - i > 0 && i < n);
860                 }
861         }
862
863         return from_utf8(fromqstr(s));
864 }
865
866
867 void GuiCompleter::popupActivated(const QString & completion)
868 {
869         Cursor cur = gui_->bufferView().cursor();
870         cur.updateFlags(Update::None);
871         
872         docstring prefix = cur.inset().completionPrefix(cur);
873         docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
874         cur.inset().insertCompletion(cur, postfix, true);
875         hidePopup(cur);
876         hideInline(cur);
877         
878         if (cur.disp_.update())
879                 gui_->bufferView().processUpdateFlags(cur.disp_.update());
880 }
881
882
883 void GuiCompleter::popupHighlighted(const QString & completion)
884 {
885         if (updateLock_ > 0)
886                 return;
887
888         Cursor cur = gui_->bufferView().cursor();
889         cur.updateFlags(Update::None);
890         
891         if (inlineVisible())
892                 updateInline(cur, completion);
893         
894         if (cur.disp_.update())
895                 gui_->bufferView().processUpdateFlags(cur.disp_.update());
896 }
897
898 } // namespace frontend
899 } // namespace lyx
900
901 #include "GuiCompleter_moc.cpp"