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