1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2019 LiteIDE. All rights reserved.
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 ** Lesser General Public License for more details.
15 **
16 ** In addition, as a special exception,  that plugins developed for LiteIDE,
17 ** are allowed to remain closed sourced and can be distributed under any license .
18 ** These rights are included in the file LGPL_EXCEPTION.txt in this package.
19 **
20 **************************************************************************/
21 // Module: codecompleter.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23 
24 #include "codecompleter.h"
25 #include "faketooltip.h"
26 #include <QApplication>
27 #include <QListView>
28 #include <QStandardItemModel>
29 #include <QKeyEvent>
30 #include <QVBoxLayout>
31 #include <QScrollBar>
32 #include <QSortFilterProxyModel>
33 #include <QItemSelectionModel>
34 #include <QDesktopWidget>
35 #include <QItemDelegate>
36 #include <QLabel>
37 #include <QDebug>
38 //lite_memory_check_begin
39 #if defined(WIN32) && defined(_MSC_VER) &&  defined(_DEBUG)
40      #define _CRTDBG_MAP_ALLOC
41      #include <stdlib.h>
42      #include <crtdbg.h>
43      #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
44      #define new DEBUG_NEW
45 #endif
46 //lite_memory_check_end
47 
CodeCompleter(QObject * parent)48 CodeCompleter::CodeCompleter(QObject *parent) :
49     QCompleter(parent)
50 {
51     this->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
52     this->setWrapAround(true);
53     m_popup = new QListView;
54     m_popup->setEditTriggers(QAbstractItemView::NoEditTriggers);
55     m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
56     m_popup->setSelectionBehavior(QAbstractItemView::SelectRows);
57     m_popup->setSelectionMode(QAbstractItemView::SingleSelection);
58     m_popup->setModelColumn(0);
59     this->setPopup(m_popup);
60     m_proxy = new QSortFilterProxyModel(this);
61 }
62 
~CodeCompleter()63 CodeCompleter::~CodeCompleter()
64 {
65 }
66 
setModel(QAbstractItemModel * c)67 void CodeCompleter::setModel(QAbstractItemModel *c)
68 {
69     m_proxy->setSourceModel(c);
70     QCompleter::setModel(m_proxy);
71 }
72 
setSeparator(const QString & separator)73 void CodeCompleter::setSeparator(const QString &separator)
74 {
75     m_seperator = separator;
76 }
77 
setCompletionPrefix(const QString & prefix)78 void CodeCompleter::setCompletionPrefix(const QString &prefix)
79 {
80     QCompleter::setCompletionPrefix(prefix);
81 }
82 
completionPrefix() const83 QString CodeCompleter::completionPrefix() const
84 {
85     return QCompleter::completionPrefix();
86 }
87 
updateFilter()88 void CodeCompleter::updateFilter()
89 {
90 
91 }
92 
eventFilter(QObject * o,QEvent * e)93 bool CodeCompleter::eventFilter(QObject *o, QEvent *e)
94 {
95     switch (e->type()) {
96     case QEvent::KeyPress: {
97         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
98         switch (ke->key()) {
99         case Qt::Key_Up:
100             if ( this->popup() && this->popup()->isVisible()) {
101                 QModelIndex index = this->popup()->currentIndex();
102                 if (index.isValid() && (index.row() == 0)) {
103                     this->popup()->setCurrentIndex(this->popup()->model()->index(this->popup()->model()->rowCount()-1,0));
104                     return true;
105                 }
106             }
107             break;
108         case Qt::Key_Down:
109             if (this->popup() && this->popup()->isVisible()) {
110                 QModelIndex index = this->popup()->currentIndex();
111                 if (index.isValid() && (index.row() == this->popup()->model()->rowCount()-1)) {
112                     this->popup()->setCurrentIndex(this->popup()->model()->index(0,0));
113                     return true;
114                 }
115             }
116             break;
117         default:
118             break;
119         }
120         break;
121     }
122     default:
123         break;
124     }
125     return QCompleter::eventFilter(o,e);
126 }
127 
separator() const128 QString CodeCompleter::separator() const
129 {
130     return m_seperator;
131 }
132 
splitPath(const QString & path) const133 QStringList CodeCompleter::splitPath(const QString &path) const
134 {
135     if (m_seperator.isNull()) {
136         return QCompleter::splitPath(path);
137     }
138     return path.split(m_seperator);
139 }
140 
pathFromIndex(const QModelIndex & index) const141 QString CodeCompleter::pathFromIndex(const QModelIndex &index) const
142 {
143     if (m_seperator.isNull()) {
144         return QCompleter::pathFromIndex(index);
145     }
146 
147     // navigate up and accumulate data
148     QStringList dataList;
149     for (QModelIndex i = index; i.isValid(); i = i.parent()) {
150         dataList.prepend(model()->data(i, completionRole()).toString());
151     }
152     return dataList.join(m_seperator);
153 }
154 
155 class CodeCompleterInfo : public FakeToolTip
156 {
157 public:
CodeCompleterInfo(QWidget * parent=0)158     CodeCompleterInfo(QWidget *parent = 0)
159         : FakeToolTip(parent), m_label(new QLabel(this))
160     {
161         QVBoxLayout *layout = new QVBoxLayout(this);
162         layout->setMargin(0);
163         layout->setSpacing(0);
164         layout->addWidget(m_label);
165 
166         // Limit horizontal width
167         m_label->setSizePolicy(QSizePolicy::Fixed, m_label->sizePolicy().verticalPolicy());
168 
169         m_label->setForegroundRole(QPalette::ToolTipText);
170         m_label->setBackgroundRole(QPalette::ToolTipBase);
171     }
172 
setText(const QString & text)173     void setText(const QString &text)
174     {
175         m_label->setText(text);
176     }
177 
178     // Workaround QTCREATORBUG-11653
calculateMaximumWidth()179     void calculateMaximumWidth()
180     {
181         const QDesktopWidget *desktopWidget = QApplication::desktop();
182         const int desktopWidth = desktopWidget->isVirtualDesktop()
183                 ? desktopWidget->width()
184                 : desktopWidget->availableGeometry(desktopWidget->primaryScreen()).width();
185         const QMargins widgetMargins = contentsMargins();
186         const QMargins layoutMargins = layout()->contentsMargins();
187         const int margins = widgetMargins.left() + widgetMargins.right()
188                 + layoutMargins.left() + layoutMargins.right();
189         m_label->setMaximumWidth(desktopWidth - this->pos().x() - margins);
190     }
191 
192 private:
193     QLabel *m_label;
194 };
195 
CodeCompleterListView(QWidget * parent)196 CodeCompleterListView::CodeCompleterListView(QWidget *parent)
197     : QListView(parent)
198 {
199     setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
200     m_infoFrame = new CodeCompleterInfo(this);
201     m_infoTimer.setInterval(100);
202     m_infoTimer.setSingleShot(true);
203     connect(&m_infoTimer, SIGNAL(timeout()), SLOT(maybeShowInfoTip()));
204 }
205 
setModel(QAbstractItemModel * model)206 void CodeCompleterListView::setModel(QAbstractItemModel *model)
207 {
208     QListView::setModel(model);
209     connect(this->selectionModel(),SIGNAL(currentChanged(QModelIndex,QModelIndex)),&m_infoTimer,SLOT(start()));
210 }
211 
calculateSize() const212 QSize CodeCompleterListView::calculateSize() const
213 {
214     static const int maxVisibleItems = 10;
215 
216     // Determine size by calculating the space of the visible items
217     const int visibleItems = qMin(model()->rowCount(), maxVisibleItems);
218     const int firstVisibleRow = verticalScrollBar()->value();
219 
220     const QStyleOptionViewItem &option = viewOptions();
221     QSize shint;
222     for (int i = 0; i < visibleItems; ++i) {
223         QSize tmp = itemDelegate()->sizeHint(option, model()->index(i + firstVisibleRow, 0));
224         if (shint.width() < tmp.width())
225             shint = tmp;
226     }
227     shint.rheight() *= visibleItems;
228     return shint;
229 }
230 
infoFramePos() const231 QPoint CodeCompleterListView::infoFramePos() const
232 {
233     const QRect &r = rectForIndex(currentIndex());
234     int xoffset = this->frameWidth()+3;
235     int yoffset = this->frameWidth()-verticalOffset();
236     QScrollBar *vsb = this->verticalScrollBar();
237     if (vsb && vsb->isVisible())
238         xoffset += this->horizontalScrollBar()->sizeHint().height();
239     QPoint pt = this->mapToGlobal(r.topRight());
240     pt.rx() += xoffset;
241     pt.ry() += yoffset;
242     return pt;
243 }
244 
maybeShowInfoTip()245 void CodeCompleterListView::maybeShowInfoTip()
246 {
247     const QModelIndex &current = this->currentIndex();
248     if (!current.isValid())
249         return;
250 
251     if (!this->isVisible()) {
252         if (m_infoFrame->isVisible()) {
253             m_infoFrame->hide();
254         }
255         m_infoTimer.setInterval(100);
256         return;
257     }
258 
259     const QString &infoTip = current.data(Qt::ToolTipRole).toString();
260     if (infoTip.isEmpty()) {
261         m_infoFrame->hide();
262         return;
263     }
264     m_infoFrame->move(this->infoFramePos());
265     m_infoFrame->setText(infoTip);
266     m_infoFrame->calculateMaximumWidth();
267     m_infoFrame->adjustSize();
268     m_infoFrame->show();
269     m_infoFrame->raise();
270     m_infoTimer.setInterval(0);
271 }
272 
hideEvent(QHideEvent * e)273 void CodeCompleterListView::hideEvent(QHideEvent *e)
274 {
275     m_infoFrame->hide();
276     QListView::hideEvent(e);
277 }
278 
279 class CodeCompleterItemDelegate : public QItemDelegate
280 {
281 public:
CodeCompleterItemDelegate(QAbstractItemView * view)282     CodeCompleterItemDelegate(QAbstractItemView *view)
283         : QItemDelegate(view), view(view) { }
paint(QPainter * p,const QStyleOptionViewItem & opt,const QModelIndex & idx) const284     void paint(QPainter *p, const QStyleOptionViewItem& opt, const QModelIndex& idx) const {
285         QStyleOptionViewItem optCopy = opt;
286         optCopy.showDecorationSelected = true;
287         if (view->currentIndex() == idx)
288             optCopy.state |= QStyle::State_HasFocus;
289         QItemDelegate::paint(p, optCopy, idx);
290     }
291 
292 private:
293     QAbstractItemView *view;
294 };
295 
CodeCompleterProxyModel(QObject * parent)296 CodeCompleterProxyModel::CodeCompleterProxyModel(QObject *parent)
297     : QAbstractListModel(parent),m_model(0)
298 {
299     m_seperator = "::";
300     m_fuzzy = false;
301 }
302 
~CodeCompleterProxyModel()303 CodeCompleterProxyModel::~CodeCompleterProxyModel()
304 {
305     clearItems();
306 }
307 
rowCount(const QModelIndex &) const308 int CodeCompleterProxyModel::rowCount(const QModelIndex &) const
309 {
310     return m_items.size();
311 }
312 
data(const QModelIndex & index,int role) const313 QVariant CodeCompleterProxyModel::data(const QModelIndex &index, int role) const
314 {
315     if (index.row() >= m_items.size())
316         return QVariant();
317     QStandardItem *item = m_items[index.row()];
318     if (role == Qt::DisplayRole) {
319         return item->text();
320     }
321     return item->data(role);
322 }
323 
setSourceModel(QStandardItemModel * model)324 void CodeCompleterProxyModel::setSourceModel(QStandardItemModel *model)
325 {
326     m_model = model;
327 }
328 
setImportList(const QStringList & importList)329 void CodeCompleterProxyModel::setImportList(const QStringList &importList)
330 {
331     m_importList = importList;
332 }
333 
sourceModel() const334 QStandardItemModel *CodeCompleterProxyModel::sourceModel() const
335 {
336     return m_model;
337 }
338 
item(const QModelIndex & index) const339 QStandardItem *CodeCompleterProxyModel::item(const QModelIndex &index) const
340 {
341     if (index.row() >= m_items.size())
342         return 0;
343     return m_items[index.row()];
344 }
345 
splitFilter(const QString & filter,QModelIndex & parent,QString & prefix,const QString & sep)346 bool CodeCompleterProxyModel::splitFilter(const QString &filter, QModelIndex &parent, QString &prefix, const QString &sep)
347 {
348     if (filter.isEmpty()) {
349         parent = QModelIndex();
350         prefix = filter;
351         return true;
352     }
353     QStringList filterList = filter.split(sep);
354     if (filterList.size() == 1) {
355         parent = QModelIndex();
356         prefix = filter;
357         return true;
358     }
359     prefix = filterList.last();
360     filterList.removeLast();
361     QStandardItem *root = 0;
362     QStandardItem *item = 0;
363     foreach (QString word, filterList) {
364         item = 0;
365         QModelIndex parent = m_model->indexFromItem(root);
366         for (int i = 0; i < m_model->rowCount(parent); i++) {
367             QModelIndex index = m_model->index(i,0,parent);
368             QStandardItem *tmp = m_model->itemFromIndex(index);
369             if (tmp->text() == word) {
370                 item = tmp;
371                 break;
372             }
373         }
374         if (item == 0) {
375             break;
376         }
377         root = item;
378     }
379     if (!item) {
380         return false;
381     }
382     parent = m_model->indexFromItem(item);
383     return true;
384 }
385 
clearItems()386 void CodeCompleterProxyModel::clearItems()
387 {
388     for (int i = 0; i < m_items.size(); i++) {
389         delete m_items[i];
390     }
391     m_items.clear();
392 }
393 
394 //copy ContentLessThan from QtCreator source
395 struct ContentLessThan
396 {
ContentLessThanContentLessThan397     ContentLessThan(const QString &prefix)
398         : m_prefix(prefix)
399     {}
400 
operator ()ContentLessThan401     bool operator()(const QStandardItem *a, const QStandardItem *b)
402     {
403         // The order is case-insensitive in principle, but case-sensitive when this
404         // would otherwise mean equality
405         const QString &lowera = a->text().toLower();
406         const QString &lowerb = b->text().toLower();
407 
408         if (!m_prefix.isEmpty()) {
409             const QString &lowerprefix = m_prefix.toLower();
410 
411             // All continuations should go before all fuzzy matches
412             if (int diff = lowera.startsWith(lowerprefix) - lowerb.startsWith(lowerprefix))
413                 return diff > 0;
414             if (int diff = a->text().startsWith(m_prefix) - b->text().startsWith(m_prefix))
415                 return diff > 0;
416         }
417         if (lowera == lowerb)
418             return lessThan(a->text(), b->text());
419         else
420             return lessThan(lowera, lowerb);
421     }
422 
lessThanContentLessThan423     bool lessThan(const QString &a, const QString &b)
424     {
425         QString::const_iterator pa = a.begin();
426         QString::const_iterator pb = b.begin();
427 
428         CharLessThan charLessThan;
429         enum { Letter, SmallerNumber, BiggerNumber } state = Letter;
430         for (; pa != a.end() && pb != b.end(); ++pa, ++pb) {
431             if (*pa == *pb)
432                 continue;
433             if (state != Letter) {
434                 if (!pa->isDigit() || !pb->isDigit())
435                     break;
436             } else if (pa->isDigit() && pb->isDigit()) {
437                 if (charLessThan(*pa, *pb))
438                     state = SmallerNumber;
439                 else
440                     state = BiggerNumber;
441             } else {
442                 return charLessThan(*pa, *pb);
443             }
444         }
445 
446         if (state == Letter)
447             return pa == a.end() && pb != b.end();
448         if (pa != a.end() && pa->isDigit())
449             return false;                   //more digits
450         if (pb != b.end() && pb->isDigit())
451             return true;                    //fewer digits
452         return state == SmallerNumber;      //same length, compare first different digit in the sequence
453     }
454 
455     struct CharLessThan
456     {
operator ()ContentLessThan::CharLessThan457         bool operator()(const QChar &a, const QChar &b)
458         {
459             if (a == QLatin1Char('_'))
460                 return false;
461             else if (b == QLatin1Char('_'))
462                 return true;
463             else
464                 return a < b;
465         }
466     };
467 
468 private:
469     QString m_prefix;
470 };
471 
filter(const QString & filter,int cs,LiteApi::CompletionContext ctx)472 int CodeCompleterProxyModel::filter(const QString &filter, int cs, LiteApi::CompletionContext ctx)
473 {
474     if (!m_model) {
475         return 0;
476     }
477 
478     clearItems();
479     if (ctx == LiteApi::CompleterImportContext) {
480         QIcon icon("icon:liteeditor/images/keyword.png");
481         if (filter.isEmpty()) {
482             foreach (QString import, m_importList) {
483                 m_items.append(new QStandardItem(icon,import));
484             }
485             return m_items.size();
486         }
487         QList<QStandardItem*> best;
488         QList<QStandardItem*> second;
489         QList<QStandardItem*> other;
490         int sep = filter.lastIndexOf("/");
491         QString root = filter.left(sep+1);
492         QString check = filter.mid(sep+1);
493         foreach (QString import, m_importList) {
494             if (import.startsWith(root)) {
495                 QString text = import.mid(sep+1);
496                 if (text.contains("/")) {
497                     foreach (QString path, text.split("/")) {
498                         int n = path.indexOf(check);
499                         if (n == 0) {
500                             if (check == path) {
501                                 best.append(new QStandardItem(icon,import));
502                             } else {
503                                 second.append(new QStandardItem(icon,import));
504                             }
505                             break;
506                         } else if (n > 0) {
507                             other.append(new QStandardItem(icon,import));
508                             break;
509                         }
510                     }
511                 } else {
512                     int n = text.indexOf(check);
513                     if (n == 0) {
514                         if (check == text) {
515                             best.append(new QStandardItem(icon,import));
516                         } else {
517                             second.append(new QStandardItem(icon,import));
518                         }
519                     } else if (n > 0) {
520                         other.append(new QStandardItem(icon,import));
521                     }
522                 }
523             }
524         }
525         qStableSort(best.begin(), best.end(), ContentLessThan(filter));
526         qStableSort(second.begin(), second.end(), ContentLessThan(filter));
527         qStableSort(other.begin(), other.end(), ContentLessThan(filter));
528         m_items.append(best);
529         m_items.append(second);
530         m_items.append(other);
531         return m_items.size();
532     }
533     QModelIndex parentIndex;
534     QString prefix;
535     if (!splitFilter(filter,parentIndex,prefix,m_seperator)) {
536         return 0;
537     }
538     m_prefix = prefix;
539     if (prefix.isEmpty()) {
540         int count = m_model->rowCount(parentIndex);
541         for (int i = 0; i < count; i++) {
542             QModelIndex index = m_model->index(i,0,parentIndex);
543             QStandardItem *item = m_model->itemFromIndex(index);
544             m_items.append(item->clone());
545         }
546         qStableSort(m_items.begin(), m_items.end(), ContentLessThan(prefix));
547         return m_items.size();
548     }
549 
550     //fuzzy completer
551     if (m_fuzzy) {
552         if (!prefix.isEmpty() && !( prefix[0].isLetter() || prefix[0] == '_') ) {
553             return 0;
554         }
555         QString keyRegExp;
556         if (!parentIndex.isValid()) {
557             keyRegExp = "^";
558         }
559         foreach (const QChar &c, prefix) {
560             keyRegExp += c;
561             keyRegExp += "[0-9a-z_]*";
562         }
563 
564         QRegExp regExp(keyRegExp);
565         regExp.setCaseSensitivity(Qt::CaseInsensitive);
566 
567         int count = m_model->rowCount(parentIndex);
568         QList<QStandardItem*> otherItems;
569         for (int i = 0; i < count; i++) {
570             QModelIndex index = m_model->index(i,0,parentIndex);
571             QStandardItem *item = m_model->itemFromIndex(index);
572             int n = regExp.indexIn(item->text());
573             if (n == 0) {
574                 m_items.append(item->clone());
575             } else if (n > 0) {
576                 otherItems.append(item->clone());
577             }
578         }
579         qStableSort(m_items.begin(), m_items.end(), ContentLessThan(prefix));
580         qStableSort(otherItems.begin(), otherItems.end(), ContentLessThan(prefix));
581         m_items.append(otherItems);
582         return m_items.size();
583     }
584 
585     QString keyRegExp;
586     keyRegExp += QLatin1Char('^');
587     bool first = true;
588     const QLatin1String uppercaseWordContinuation("[a-z0-9_]*");
589     const QLatin1String lowercaseWordContinuation("(?:[a-zA-Z0-9]*_)?");
590     foreach (const QChar &c, prefix) {
591         if (cs == LiteApi::CaseInsensitive ||
592                 (cs == LiteApi::FirstLetterCaseSensitive && !first)) {
593 
594             keyRegExp += QLatin1String("(?:");
595             if (!first)
596                 keyRegExp += uppercaseWordContinuation;
597             keyRegExp += QRegExp::escape(c.toUpper());
598             keyRegExp += QLatin1Char('|');
599             if (!first)
600                 keyRegExp += lowercaseWordContinuation;
601             keyRegExp += QRegExp::escape(c.toLower());
602             keyRegExp += QLatin1Char(')');
603         } else {
604             if (!first) {
605                 if (c.isUpper())
606                     keyRegExp += uppercaseWordContinuation;
607                 else
608                     keyRegExp += lowercaseWordContinuation;
609             }
610             keyRegExp += QRegExp::escape(c);
611         }
612 
613         first = false;
614     }
615 
616     QRegExp regExp(keyRegExp);
617 
618     int count = m_model->rowCount(parentIndex);
619     for (int i = 0; i < count; i++) {
620         QModelIndex index = m_model->index(i,0,parentIndex);
621         QStandardItem *item = m_model->itemFromIndex(index);
622         if (regExp.indexIn(item->text()) == 0) {
623             m_items.append(item->clone());
624         }
625     }
626     qStableSort(m_items.begin(), m_items.end(), ContentLessThan(prefix));
627 
628     return m_items.size();
629 }
630 
setSeparator(const QString & separator)631 void CodeCompleterProxyModel::setSeparator(const QString &separator)
632 {
633     m_seperator = separator;
634 }
635 
separator() const636 QString CodeCompleterProxyModel::separator() const
637 {
638     return m_seperator;
639 }
640 
setFuzzy(bool b)641 void CodeCompleterProxyModel::setFuzzy(bool b)
642 {
643     m_fuzzy = b;
644 }
645 
isFuzzy() const646 bool CodeCompleterProxyModel::isFuzzy() const
647 {
648     return m_fuzzy;
649 }
650 
lastPrefix() const651 QString CodeCompleterProxyModel::lastPrefix() const
652 {
653     return m_prefix;
654 }
655 
CodeCompleterEx(QObject * parent)656 CodeCompleterEx::CodeCompleterEx(QObject *parent)
657     : QObject(parent), m_widget(0)
658 {
659     maxVisibleItems = 10;
660     m_eatFocusOut = true;
661     m_hiddenBecauseNoMatch = false;
662     m_cs = Qt::CaseInsensitive;
663     m_ctx = LiteApi::CompleterCodeContext;
664     m_wrap = true;
665     m_popup = new CodeCompleterListView;
666     m_popup->setUniformItemSizes(true);
667     m_popup->setEditTriggers(QAbstractItemView::NoEditTriggers);
668     m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
669     m_popup->setSelectionBehavior(QAbstractItemView::SelectRows);
670     m_popup->setSelectionMode(QAbstractItemView::SingleSelection);
671     m_popup->setItemDelegate(new CodeCompleterItemDelegate(m_popup));
672     m_popup->setModelColumn(0);
673 
674     m_popup->setParent(0, Qt::Popup);
675     m_popup->setFocusPolicy(Qt::NoFocus);
676 
677     m_popup->installEventFilter(this);
678 
679     m_proxy = new CodeCompleterProxyModel(this);
680     m_popup->setModel(m_proxy);
681 
682     QObject::connect(m_popup, SIGNAL(clicked(QModelIndex)),
683                      this, SLOT(completerActivated(QModelIndex)));
684     QObject::connect(m_popup, SIGNAL(activated(QModelIndex)),
685                      m_popup, SLOT(hide()));
686 
687 }
688 
~CodeCompleterEx()689 CodeCompleterEx::~CodeCompleterEx()
690 {
691     if (m_popup) {
692         delete m_popup;
693     }
694 }
695 
setModel(QStandardItemModel * c)696 void CodeCompleterEx::setModel(QStandardItemModel *c)
697 {
698     m_proxy->setSourceModel(c);
699 }
700 
model() const701 QAbstractItemModel *CodeCompleterEx::model() const
702 {
703     return m_proxy->sourceModel();
704 }
705 
setImportList(const QStringList & importList)706 void CodeCompleterEx::setImportList(const QStringList &importList)
707 {
708     m_proxy->setImportList(importList);
709 }
710 
setSeparator(const QString & separator)711 void CodeCompleterEx::setSeparator(const QString &separator)
712 {
713     m_proxy->setSeparator(separator);
714 }
715 
separator() const716 QString CodeCompleterEx::separator() const
717 {
718     return m_proxy->separator();
719 }
720 
setCompletionPrefix(const QString & prefix)721 void CodeCompleterEx::setCompletionPrefix(const QString &prefix)
722 {
723     m_prefix = prefix;
724     if (m_proxy->filter(prefix,m_cs,m_ctx) <= 0) {
725         if (m_popup->isVisible()) {
726             m_popup->close();
727         }
728         return;
729     }
730     m_popup->reset();
731 }
732 
completionPrefix() const733 QString CodeCompleterEx::completionPrefix() const
734 {
735     return m_prefix;
736 }
737 
lastPrefix() const738 QString CodeCompleterEx::lastPrefix() const
739 {
740     return m_proxy->lastPrefix();
741 }
742 
setCompletionContext(LiteApi::CompletionContext ctx)743 void CodeCompleterEx::setCompletionContext(LiteApi::CompletionContext ctx)
744 {
745     m_ctx = ctx;
746 }
747 
completionContext() const748 LiteApi::CompletionContext CodeCompleterEx::completionContext() const
749 {
750     return m_ctx;
751 }
752 
updateFilter()753 void CodeCompleterEx::updateFilter()
754 {
755     if (m_proxy->filter(m_prefix,m_cs) <= 0) {
756         if (m_popup->isVisible()) {
757             m_popup->close();
758         }
759         return;
760     }
761     m_popup->reset();
762 }
763 
complete(const QRect & rect)764 void CodeCompleterEx::complete(const QRect &rect)
765 {
766     if (m_proxy->rowCount() == 0) {
767         return;
768     }
769 
770     const QRect screen = QApplication::desktop()->availableGeometry(m_widget);
771     Qt::LayoutDirection dir = m_widget->layoutDirection();
772     QPoint pos;
773     int rh, w;
774     int h = (m_popup->sizeHintForRow(0) * qMin(maxVisibleItems, m_popup->model()->rowCount()) + 3)+3;
775     QScrollBar *hsb = m_popup->horizontalScrollBar();
776     if (hsb && hsb->isVisible())
777         h += m_popup->horizontalScrollBar()->sizeHint().height();
778 
779     if (rect.isValid()) {
780         rh = rect.height();
781         w = rect.width();
782         pos = m_widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
783     } else {
784         rh = m_widget->height();
785         pos = m_widget->mapToGlobal(QPoint(0, m_widget->height() - 2));
786         w = m_widget->width();
787     }
788 
789     if (w > screen.width())
790         w = screen.width();
791     if ((pos.x() + w) > (screen.x() + screen.width()))
792         pos.setX(screen.x() + screen.width() - w);
793     if (pos.x() < screen.x())
794         pos.setX(screen.x());
795 
796     int top = pos.y() - rh - screen.top() + 2;
797     int bottom = screen.bottom() - pos.y();
798     h = qMax(h, m_popup->minimumHeight());
799     if (h > bottom) {
800         h = qMin(qMax(top, bottom), h);
801 
802         if (top > bottom)
803             pos.setY(pos.y() - h - rh + 2);
804     }
805     w = qMax(w,200);
806     m_popup->setGeometry(pos.x(), pos.y(), w, h);
807 
808     if (!m_popup->isVisible())
809         m_popup->show();
810 }
811 
widget() const812 QWidget *CodeCompleterEx::widget() const
813 {
814     return m_widget;
815 }
816 
setWidget(QWidget * widget)817 void CodeCompleterEx::setWidget(QWidget *widget)
818 {
819     if (m_widget == widget) {
820         return;
821     }
822     if (m_widget) {
823         m_widget->removeEventFilter(this);
824     }
825     m_widget = widget;
826 //    Qt::FocusPolicy origPolicy = Qt::NoFocus;
827 //    if (widget)
828 //        origPolicy = widget->focusPolicy();
829 //    m_popup->setParent(0, Qt::Popup);
830 //    m_popup->setFocusPolicy(Qt::NoFocus);
831 //    if (widget)
832 //        widget->setFocusPolicy(origPolicy);
833     if (m_widget) {
834         m_widget->installEventFilter(this);
835         m_popup->setFocusProxy(m_widget);
836     }
837 }
838 
currentIndex() const839 QModelIndex CodeCompleterEx::currentIndex() const
840 {
841     return m_popup->currentIndex();
842 }
843 
currentCompletion() const844 QString CodeCompleterEx::currentCompletion() const
845 {
846     QModelIndex index = m_popup->currentIndex();
847     if (index.isValid()) {
848         QStandardItem *item = m_proxy->item(index);
849         if (item) {
850             return item->text();
851         }
852     }
853     return QString();
854 }
855 
setCaseSensitivity(Qt::CaseSensitivity cs)856 void CodeCompleterEx::setCaseSensitivity(Qt::CaseSensitivity cs)
857 {
858     m_cs = cs;
859 }
860 
caseSensitivity() const861 Qt::CaseSensitivity CodeCompleterEx::caseSensitivity() const
862 {
863     return m_cs;
864 }
865 
popup() const866 QAbstractItemView *CodeCompleterEx::popup() const
867 {
868     return m_popup;
869 }
870 
completionModel() const871 QAbstractItemModel *CodeCompleterEx::completionModel() const
872 {
873     return m_proxy;
874 }
875 
setWrapAround(bool wrap)876 void CodeCompleterEx::setWrapAround(bool wrap)
877 {
878     m_wrap = wrap;
879 }
880 
wrapAround() const881 bool CodeCompleterEx::wrapAround() const
882 {
883     return m_wrap;
884 }
885 
setFuzzy(bool b)886 void CodeCompleterEx::setFuzzy(bool b)
887 {
888     m_proxy->setFuzzy(b);
889 }
890 
isFuzzy() const891 bool CodeCompleterEx::isFuzzy() const
892 {
893     return m_proxy->isFuzzy();
894 }
895 
completerActivated(QModelIndex index)896 void CodeCompleterEx::completerActivated(QModelIndex index)
897 {
898     if (m_popup->isVisible()) {
899         m_popup->close();
900     }
901     emit activated(index);
902 }
903 
eventFilter(QObject * o,QEvent * e)904 bool CodeCompleterEx::eventFilter(QObject *o, QEvent *e)
905 {
906     if (m_eatFocusOut && o == m_widget && e->type() == QEvent::FocusOut) {
907         m_hiddenBecauseNoMatch = false;
908         if (m_popup && m_popup->isVisible())
909             return true;
910     }
911 
912     if (o != m_popup)
913         return QObject::eventFilter(o, e);
914 
915     switch (e->type()) {
916     case QEvent::KeyPress: {
917         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
918 
919         QModelIndex curIndex = m_popup->currentIndex();
920         //QModelIndexList selList = m_popup->selectionModel()->selectedIndexes();
921 
922         const int key = ke->key();
923         // In UnFilteredPopup mode, select the current item
924 //        if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid()
925 //            && m_mode == QCompleter::UnfilteredPopupCompletion) {
926 //            m_popup->setCurrentIndex(curIndex);
927 //              return true;
928 //        }
929 
930         // Handle popup navigation keys. These are hardcoded because up/down might make the
931         // widget do something else (lineedit cursor moves to home/end on mac, for instance)
932         switch (key) {
933         case Qt::Key_End:
934         case Qt::Key_Home:
935             if (ke->modifiers() & Qt::ControlModifier)
936                 return false;
937             break;
938         case Qt::Key_N:
939         case Qt::Key_P:
940             // select next/previous completion
941             if (ke->modifiers() == Qt::ControlModifier) {
942                 int change = (ke->key() == Qt::Key_N) ? 1 : -1;
943                 int nrows = m_proxy->rowCount();
944                 int row = m_popup->currentIndex().row();
945                 int newRow = (row + change + nrows) % nrows;
946                 if (newRow == row + change || !ke->isAutoRepeat()) {
947                     QModelIndex index = m_proxy->index(newRow, 0);
948                     m_popup->setCurrentIndex(index);
949                 }
950                 return true;
951             }
952             break;
953 
954         case Qt::Key_Up:
955             if (curIndex.row() == 0) {
956                 if (m_wrap) {
957                     int rowCount = m_proxy->rowCount();
958                     QModelIndex lastIndex = m_proxy->index(rowCount - 1, 0);
959                     m_popup->setCurrentIndex(lastIndex);
960                 }
961                 return true;
962             }
963             return false;
964 
965         case Qt::Key_Down:
966             if (curIndex.row() == m_proxy->rowCount() - 1) {
967                 if (m_wrap) {
968                     QModelIndex firstIndex = m_proxy->index(0, 0);
969                     m_popup->setCurrentIndex(firstIndex);
970                 }
971                 return true;
972             }
973             return false;
974 
975         case Qt::Key_PageUp:
976         case Qt::Key_PageDown:
977             return false;
978         }
979 
980         // Send the event to the widget. If the widget accepted the event, do nothing
981         // If the widget did not accept the event, provide a default implementation
982         m_eatFocusOut = false;
983         (static_cast<QObject *>(m_widget))->event(ke);
984         m_eatFocusOut = true;
985         if (!m_widget || e->isAccepted() || !m_popup->isVisible()) {
986             // widget lost focus, hide the popup
987             if (m_widget && (!m_widget->hasFocus()
988 #ifdef QT_KEYPAD_NAVIGATION
989                 || (QApplication::keypadNavigationEnabled() && !m_widget->hasEditFocus())
990 #endif
991                 ))
992                 m_popup->hide();
993             if (e->isAccepted())
994                 return true;
995         }
996 
997         // default implementation for keys not handled by the widget when popup is open
998         switch (key) {
999 #ifdef QT_KEYPAD_NAVIGATION
1000         case Qt::Key_Select:
1001             if (!QApplication::keypadNavigationEnabled())
1002                 break;
1003 #endif
1004         case Qt::Key_Return:
1005         case Qt::Key_Enter:
1006         case Qt::Key_Tab:
1007             m_popup->hide();
1008             if (curIndex.isValid())
1009                 //m__q_complete(curIndex);
1010                 this->completerActivated(curIndex);
1011             break;
1012 
1013         case Qt::Key_F4:
1014             if (ke->modifiers() & Qt::AltModifier)
1015                 m_popup->hide();
1016             break;
1017 
1018         case Qt::Key_Backtab:
1019         case Qt::Key_Escape:
1020             m_popup->hide();
1021             break;
1022 
1023         default:
1024             break;
1025         }
1026 
1027         return true;
1028     }
1029 
1030 #ifdef QT_KEYPAD_NAVIGATION
1031     case QEvent::KeyRelease: {
1032         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1033         if (QApplication::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) {
1034             // Send the event to the 'widget'. This is what we did for KeyPress, so we need
1035             // to do the same for KeyRelease, in case the widget's KeyPress event set
1036             // up something (such as a timer) that is relying on also receiving the
1037             // key release. I see this as a bug in Qt, and should really set it up for all
1038             // the affected keys. However, it is difficult to tell how this will affect
1039             // existing code, and I can't test for every combination!
1040             m_eatFocusOut = false;
1041             static_cast<QObject *>(m_widget)->event(ke);
1042             m_eatFocusOut = true;
1043         }
1044         break;
1045     }
1046 #endif
1047     case QEvent::MouseButtonPress: {
1048 #ifdef QT_KEYPAD_NAVIGATION
1049         if (QApplication::keypadNavigationEnabled()) {
1050             // if we've clicked in the widget (or its descendant), let it handle the click
1051             QWidget *source = qobject_cast<QWidget *>(o);
1052             if (source) {
1053                 QPoint pos = source->mapToGlobal((static_cast<QMouseEvent *>(e))->pos());
1054                 QWidget *target = QApplication::widgetAt(pos);
1055                 if (target && (m_widget->isAncestorOf(target) ||
1056                     target == m_widget)) {
1057                     m_eatFocusOut = false;
1058                     static_cast<QObject *>(target)->event(e);
1059                     m_eatFocusOut = true;
1060                     return true;
1061                 }
1062             }
1063         }
1064 #endif
1065         if (!m_popup->underMouse()) {
1066             m_popup->hide();
1067             return true;
1068         }
1069         }
1070         return false;
1071 
1072     case QEvent::InputMethod:
1073         QApplication::sendEvent(m_widget, e);
1074         break;
1075     case QEvent::ShortcutOverride:    {
1076         QKeyEvent *ke = static_cast<QKeyEvent *>(e);
1077         switch (ke->key()) {
1078         case Qt::Key_Escape:
1079             e->accept();
1080             return  true;
1081         case Qt::Key_N:
1082         case Qt::Key_P:
1083             if (ke->modifiers() == Qt::ControlModifier) {
1084                 e->accept();
1085                 return true;
1086             }
1087         }
1088         QApplication::sendEvent(m_widget, e);
1089         break;
1090         }
1091     default:
1092         return false;
1093     }
1094     return false;
1095 }
1096