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