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