3 This file is part of Minitube.
4 Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 Minitube 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
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Minitube. If not, see <http://www.gnu.org/licenses/>.
20 #include "autocomplete.h"
21 #include "suggester.h"
22 #ifdef APP_MAC_SEARCHFIELD
23 #include "searchlineedit_mac.h"
25 #include "searchlineedit.h"
32 #include <QListWidget>
34 #ifndef QT_NO_DEBUG_OUTPUT
35 /// Gives human-readable event type information.
36 QDebug operator<<(QDebug str, const QEvent *ev) {
37 static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type");
40 QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
48 return str.maybeSpace();
52 AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit)
53 : QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0),
55 popup = new QListWidget();
56 popup->setWindowFlags(Qt::Popup);
57 popup->setFocusProxy(buddy->toWidget());
58 popup->installEventFilter(this);
59 buddy->toWidget()->window()->installEventFilter(this);
60 popup->setMouseTracking(true);
63 popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
64 popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
65 popup->setWindowOpacity(.9);
66 popup->setProperty("suggest", true);
68 connect(popup, SIGNAL(itemClicked(QListWidgetItem *)), SLOT(acceptSuggestion()));
69 connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
70 SLOT(currentItemChanged(QListWidgetItem *)));
71 connect(popup, SIGNAL(itemEntered(QListWidgetItem *)), SLOT(itemEntered(QListWidgetItem *)));
73 timer = new QTimer(this);
74 timer->setSingleShot(true);
75 timer->setInterval(500);
76 connect(timer, SIGNAL(timeout()), SLOT(suggest()));
77 connect(buddy->toWidget(), SIGNAL(textEdited(QString)), timer, SLOT(start()));
80 bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
95 if (ev->type() == QEvent::Leave) {
96 popup->setCurrentItem(0);
97 popup->clearSelection();
98 if (!originalText.isEmpty()) buddy->setText(originalText);
102 if (ev->type() == QEvent::FocusOut || ev->type() == QEvent::MouseButtonPress) {
107 if (ev->type() == QEvent::KeyPress) {
108 bool consumed = false;
109 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
110 // qWarning() << keyEvent->text();
111 switch (keyEvent->key()) {
114 if (popup->currentItem()) {
129 if (popup->currentRow() == 0) {
130 popup->setCurrentItem(0);
131 popup->clearSelection();
132 buddy->setText(originalText);
133 buddy->toWidget()->setFocus();
142 case Qt::Key_PageDown:
147 // qDebug() << keyEvent->text();
159 void AutoComplete::showSuggestions(const QVector<Suggestion *> &suggestions) {
160 if (suggestions.isEmpty()) {
164 popup->setUpdatesEnabled(false);
166 for (int i = 0; i < suggestions.count(); ++i) {
167 QListWidgetItem *item = new QListWidgetItem(popup);
168 Suggestion *s = suggestions[i];
169 item->setText(s->value);
170 if (!s->type.isEmpty()) item->setIcon(QIcon(":/images/" + s->type + ".png"));
172 popup->setCurrentItem(nullptr);
173 int h = popup->frameWidth() * 2;
174 for (int i = 0; i < suggestions.count(); ++i)
175 h += popup->sizeHintForRow(i);
177 popup->resize(buddy->toWidget()->width(), h);
179 popup->setUpdatesEnabled(true);
181 if (popup->isHidden()) {
182 itemHovering = false;
184 QTimer::singleShot(100, this, SLOT(enableItemHovering()));
188 void AutoComplete::acceptSuggestion() {
189 int index = popup->currentIndex().row();
190 if (index >= 0 && index < suggestions.size()) {
191 Suggestion *suggestion = suggestions.at(index);
192 buddy->setText(suggestion->value);
193 emit suggestionAccepted(suggestion);
194 emit suggestionAccepted(suggestion->value);
195 originalText.clear();
198 qWarning() << "No suggestion for index" << index;
201 void AutoComplete::preventSuggest() {
207 void AutoComplete::enableSuggest() {
211 void AutoComplete::setSuggester(Suggester *suggester) {
212 if (this->suggester) this->suggester->disconnect();
213 this->suggester = suggester;
214 connect(suggester, SIGNAL(ready(QVector<Suggestion *>)),
215 SLOT(suggestionsReady(QVector<Suggestion *>)));
218 void AutoComplete::suggest() {
219 if (!enabled) return;
221 popup->setCurrentItem(nullptr);
222 popup->clearSelection();
224 originalText = buddy->text();
225 if (originalText.isEmpty()) {
230 if (suggester) suggester->suggest(originalText);
233 void AutoComplete::suggestionsReady(const QVector<Suggestion *> &suggestions) {
234 qDeleteAll(this->suggestions);
235 this->suggestions = suggestions;
236 if (!enabled) return;
237 if (!buddy->toWidget()->hasFocus() && buddy->toWidget()->isVisible()) return;
238 showSuggestions(suggestions);
241 void AutoComplete::adjustPosition() {
242 popup->move(buddy->toWidget()->mapToGlobal(QPoint(0, buddy->toWidget()->height())));
245 void AutoComplete::enableItemHovering() {
249 void AutoComplete::hideSuggestions() {
250 itemHovering = false;
252 mac::fadeOutWindow(popup);
257 if (!originalText.isEmpty()) {
258 buddy->setText(originalText);
259 originalText.clear();
261 buddy->toWidget()->setFocus();
265 void AutoComplete::itemEntered(QListWidgetItem *item) {
266 if (!itemHovering) return;
268 item->setSelected(true);
269 popup->setCurrentItem(item);
272 void AutoComplete::currentItemChanged(QListWidgetItem *item) {
274 buddy->setText(item->text());
275 // lineEdit->setSelection(originalText.length(), lineEdit->text().length());