]> git.sur5r.net Git - minitube/blob - src/autocomplete.cpp
1cd6dd4ee678782605268e3e3276533e2f810a39
[minitube] / src / autocomplete.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
5
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.
10
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.
15
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/>.
18
19 $END_LICENSE */
20 #include "autocomplete.h"
21 #include "suggester.h"
22 #ifdef APP_MAC
23 #include "searchlineedit_mac.h"
24 #include "macutils.h"
25 #else
26 #include "searchlineedit.h"
27 #endif
28
29 #include <QListWidget>
30
31 #ifndef QT_NO_DEBUG_OUTPUT
32 /// Gives human-readable event type information.
33 QDebug operator<<(QDebug str, const QEvent * ev) {
34     static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type");
35     str << "QEvent";
36     if (ev) {
37         QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
38         if (!name.isEmpty()) str << name; else str << ev->type();
39     } else {
40         str << (void*)ev;
41     }
42     return str.maybeSpace();
43 }
44 #endif
45
46 AutoComplete::AutoComplete(SearchLineEdit *buddy, QLineEdit *lineEdit):
47     QObject(buddy), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
48
49     popup = new QListWidget();
50     popup->setWindowFlags(Qt::Popup);
51     popup->setFocusProxy(buddy);
52     popup->installEventFilter(this);
53     buddy->window()->installEventFilter(this);
54     popup->setMouseTracking(true);
55
56     // style
57     popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
58     popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
59     popup->setWindowOpacity(.9);
60     popup->setProperty("suggest", true);
61
62     connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(acceptSuggestion()));
63     connect(popup, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
64             SLOT(currentItemChanged(QListWidgetItem*)));
65     connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(itemEntered(QListWidgetItem *)));
66
67     timer = new QTimer(this);
68     timer->setSingleShot(true);
69     timer->setInterval(500);
70     connect(timer, SIGNAL(timeout()), SLOT(suggest()));
71     connect(buddy, SIGNAL(textEdited(QString)), timer, SLOT(start()));
72 }
73
74 bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
75
76     if (obj != popup) {
77         switch (ev->type()) {
78         case QEvent::Move:
79         case QEvent::Resize:
80             adjustPosition();
81             break;
82         default:
83             break;
84         }
85         return false;
86     }
87
88     // qDebug() << ev;
89
90     if (ev->type() == QEvent::Leave) {
91         popup->setCurrentItem(0);
92         popup->clearSelection();
93         if (!originalText.isEmpty()) buddy->setText(originalText);
94         return true;
95     }
96
97     if (ev->type() == QEvent::FocusOut || ev->type() == QEvent::MouseButtonPress) {
98         hideSuggestions();
99         return true;
100     }
101
102     if (ev->type() == QEvent::KeyPress) {
103         bool consumed = false;
104         QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);
105         // qWarning() << keyEvent->text();
106         switch (keyEvent->key()) {
107         case Qt::Key_Enter:
108         case Qt::Key_Return:
109             if (popup->currentItem()) {
110                 acceptSuggestion();
111                 consumed = true;
112             } else {
113                 lineEdit->event(ev);
114                 hideSuggestions();
115             }
116             break;
117
118         case Qt::Key_Escape:
119             hideSuggestions();
120             consumed = true;
121             break;
122
123         case Qt::Key_Up:
124             if (popup->currentRow() == 0) {
125                 popup->setCurrentItem(0);
126                 popup->clearSelection();
127                 buddy->setText(originalText);
128                 buddy->setFocus();
129                 consumed = true;
130             }
131             break;
132
133         case Qt::Key_Down:
134         case Qt::Key_Home:
135         case Qt::Key_End:
136         case Qt::Key_PageUp:
137         case Qt::Key_PageDown:
138             // qDebug() << key;
139             break;
140
141         default:
142             // qDebug() << keyEvent->text();
143             lineEdit->event(ev);
144             consumed = true;
145             break;
146         }
147
148         return consumed;
149     }
150
151     return false;
152 }
153
154 void AutoComplete::showSuggestions(const QList<Suggestion *> &suggestions) {
155     if (suggestions.isEmpty()) {
156         hideSuggestions();
157         return;
158     }
159     popup->setUpdatesEnabled(false);
160     popup->clear();
161     for (int i = 0; i < suggestions.count(); ++i) {
162         QListWidgetItem *item = new QListWidgetItem(popup);
163         Suggestion *s = suggestions[i];
164         item->setText(s->value);
165         if (!s->type.isEmpty())
166             item->setIcon(QIcon(":/images/" + s->type + ".png"));
167     }
168     popup->setCurrentItem(0);
169     int h = popup->frameWidth() * 2;
170     for (int i = 0; i < suggestions.count(); ++i)
171         h += popup->sizeHintForRow(i);
172
173     popup->resize(buddy->width(), h);
174     adjustPosition();
175     popup->setUpdatesEnabled(true);
176
177     if (popup->isHidden()) {
178         itemHovering = false;
179         popup->show();
180         QTimer::singleShot(100, this, SLOT(enableItemHovering()));
181     }
182 }
183
184 void AutoComplete::acceptSuggestion() {
185     int index = popup->currentIndex().row();
186     if (index >= 0 && index < suggestions.size()) {
187         Suggestion* suggestion = suggestions.at(index);
188         buddy->setText(suggestion->value);
189         emit suggestionAccepted(suggestion);
190         emit suggestionAccepted(suggestion->value);
191         originalText.clear();
192         hideSuggestions();
193     } else qWarning() << "No suggestion for index" << index;
194 }
195
196 void AutoComplete::preventSuggest() {
197     timer->stop();
198     enabled = false;
199     popup->hide();
200 }
201
202 void AutoComplete::enableSuggest() {
203     enabled = true;
204 }
205
206 void AutoComplete::setSuggester(Suggester* suggester) {
207     if (this->suggester) this->suggester->disconnect();
208     this->suggester = suggester;
209     connect(suggester, SIGNAL(ready(QList<Suggestion*>)), SLOT(suggestionsReady(QList<Suggestion*>)));
210 }
211
212 void AutoComplete::suggest() {
213     if (!enabled) return;
214
215     popup->setCurrentItem(0);
216     popup->clearSelection();
217
218     originalText = buddy->text();
219     if (originalText.isEmpty()) {
220         hideSuggestions();
221         return;
222     }
223
224     if (suggester) suggester->suggest(originalText);
225 }
226
227 void AutoComplete::suggestionsReady(const QList<Suggestion *> &suggestions) {
228     qDeleteAll(this->suggestions);
229     this->suggestions = suggestions;
230     if (!enabled) return;
231     if (!buddy->hasFocus()) return;
232     showSuggestions(suggestions);
233 }
234
235 void AutoComplete::adjustPosition() {
236     popup->move(buddy->mapToGlobal(QPoint(0, buddy->height())));
237 }
238
239 void AutoComplete::enableItemHovering() {
240     itemHovering = true;
241 }
242
243 void AutoComplete::hideSuggestions() {
244     itemHovering = false;
245 #ifdef APP_MAC
246     mac::fadeOutWindow(popup);
247 #else
248     popup->hide();
249     popup->clear();
250 #endif
251     if (!originalText.isEmpty()) {
252         buddy->setText(originalText);
253         originalText.clear();
254     }
255     buddy->setFocus();
256     timer->stop();
257 }
258
259 void AutoComplete::itemEntered(QListWidgetItem *item) {
260     if (!itemHovering) return;
261     if (!item) return;
262     item->setSelected(true);
263     popup->setCurrentItem(item);
264 }
265
266 void AutoComplete::currentItemChanged(QListWidgetItem *item) {
267     if (!item) return;
268     buddy->setText(item->text());
269     // lineEdit->setSelection(originalText.length(), lineEdit->text().length());
270 }