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