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());
41 if (!name.isEmpty()) str << name; else str << ev->type();
45 return str.maybeSpace();
49 AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit):
50 QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
52 popup = new QListWidget();
53 popup->setWindowFlags(Qt::Popup);
54 popup->setFocusProxy(buddy->toWidget());
55 popup->installEventFilter(this);
56 buddy->toWidget()->window()->installEventFilter(this);
57 popup->setMouseTracking(true);
60 popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
61 popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
62 popup->setWindowOpacity(.9);
63 popup->setProperty("suggest", true);
65 connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(acceptSuggestion()));
66 connect(popup, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
67 SLOT(currentItemChanged(QListWidgetItem*)));
68 connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(itemEntered(QListWidgetItem *)));
70 timer = new QTimer(this);
71 timer->setSingleShot(true);
72 timer->setInterval(500);
73 connect(timer, SIGNAL(timeout()), SLOT(suggest()));
74 connect(buddy->toWidget(), SIGNAL(textEdited(QString)), timer, SLOT(start()));
77 bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
93 if (ev->type() == QEvent::Leave) {
94 popup->setCurrentItem(0);
95 popup->clearSelection();
96 if (!originalText.isEmpty()) buddy->setText(originalText);
100 if (ev->type() == QEvent::FocusOut || ev->type() == QEvent::MouseButtonPress) {
105 if (ev->type() == QEvent::KeyPress) {
106 bool consumed = false;
107 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);
108 // qWarning() << keyEvent->text();
109 switch (keyEvent->key()) {
112 if (popup->currentItem()) {
127 if (popup->currentRow() == 0) {
128 popup->setCurrentItem(0);
129 popup->clearSelection();
130 buddy->setText(originalText);
131 buddy->toWidget()->setFocus();
140 case Qt::Key_PageDown:
145 // qDebug() << keyEvent->text();
157 void AutoComplete::showSuggestions(const QVector<Suggestion *> &suggestions) {
158 if (suggestions.isEmpty()) {
162 popup->setUpdatesEnabled(false);
164 for (int i = 0; i < suggestions.count(); ++i) {
165 QListWidgetItem *item = new QListWidgetItem(popup);
166 Suggestion *s = suggestions[i];
167 item->setText(s->value);
168 if (!s->type.isEmpty())
169 item->setIcon(QIcon(":/images/" + s->type + ".png"));
171 popup->setCurrentItem(0);
172 int h = popup->frameWidth() * 2;
173 for (int i = 0; i < suggestions.count(); ++i)
174 h += popup->sizeHintForRow(i);
176 popup->resize(buddy->toWidget()->width(), h);
178 popup->setUpdatesEnabled(true);
180 if (popup->isHidden()) {
181 itemHovering = false;
183 QTimer::singleShot(100, this, SLOT(enableItemHovering()));
187 void AutoComplete::acceptSuggestion() {
188 int index = popup->currentIndex().row();
189 if (index >= 0 && index < suggestions.size()) {
190 Suggestion* suggestion = suggestions.at(index);
191 buddy->setText(suggestion->value);
192 emit suggestionAccepted(suggestion);
193 emit suggestionAccepted(suggestion->value);
194 originalText.clear();
196 } else qWarning() << "No suggestion for index" << index;
199 void AutoComplete::preventSuggest() {
205 void AutoComplete::enableSuggest() {
209 void AutoComplete::setSuggester(Suggester* suggester) {
210 if (this->suggester) this->suggester->disconnect();
211 this->suggester = suggester;
212 connect(suggester, SIGNAL(ready(QVector<Suggestion*>)), SLOT(suggestionsReady(QVector<Suggestion*>)));
215 void AutoComplete::suggest() {
216 if (!enabled) return;
218 popup->setCurrentItem(0);
219 popup->clearSelection();
221 originalText = buddy->text();
222 if (originalText.isEmpty()) {
227 if (suggester) suggester->suggest(originalText);
230 void AutoComplete::suggestionsReady(const QVector<Suggestion *> &suggestions) {
231 qDeleteAll(this->suggestions);
232 this->suggestions = suggestions;
233 if (!enabled) return;
234 if (!buddy->toWidget()->hasFocus() && buddy->toWidget()->isVisible()) return;
235 showSuggestions(suggestions);
238 void AutoComplete::adjustPosition() {
239 popup->move(buddy->toWidget()->mapToGlobal(QPoint(0, buddy->toWidget()->height())));
242 void AutoComplete::enableItemHovering() {
246 void AutoComplete::hideSuggestions() {
247 itemHovering = false;
249 mac::fadeOutWindow(popup);
254 if (!originalText.isEmpty()) {
255 buddy->setText(originalText);
256 originalText.clear();
258 buddy->toWidget()->setFocus();
262 void AutoComplete::itemEntered(QListWidgetItem *item) {
263 if (!itemHovering) return;
265 item->setSelected(true);
266 popup->setCurrentItem(item);
269 void AutoComplete::currentItemChanged(QListWidgetItem *item) {
271 buddy->setText(item->text());
272 // lineEdit->setSelection(originalText.length(), lineEdit->text().length());