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