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