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