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 ¤t = 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