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