]> git.sur5r.net Git - minitube/blob - src/autocomplete.cpp
50f4e72d7c29326a115fbdcba5c67286e187129d
[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_SEARCHFIELD
23 #include "searchlineedit_mac.h"
24 #else
25 #include "searchlineedit.h"
26 #endif
27
28 #ifdef APP_MAC
29 #include "macutils.h"
30 #endif
31
32 #include <QListWidget>
33
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");
38     str << "QEvent";
39     if (ev) {
40         QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
41         if (!name.isEmpty()) str << name; else str << ev->type();
42     } else {
43         str << (void*)ev;
44     }
45     return str.maybeSpace();
46 }
47 #endif
48
49 AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit):
50     QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
51
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);
58
59     // style
60     popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
61     popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
62     popup->setWindowOpacity(.9);
63     popup->setProperty("suggest", true);
64
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 *)));
69
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()));
75 }
76
77 bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
78
79     if (obj != popup) {
80         switch (ev->type()) {
81         case QEvent::Move:
82         case QEvent::Resize:
83             adjustPosition();
84             break;
85         default:
86             break;
87         }
88         return false;
89     }
90
91     // qDebug() << ev;
92
93     if (ev->type() == QEvent::Leave) {
94         popup->setCurrentItem(0);
95         popup->clearSelection();
96         if (!originalText.isEmpty()) buddy->setText(originalText);
97         return true;
98     }
99
100     if (ev->type() == QEvent::FocusOut || ev->type() == QEvent::MouseButtonPress) {
101         hideSuggestions();
102         return true;
103     }
104
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()) {
110         case Qt::Key_Enter:
111         case Qt::Key_Return:
112             if (popup->currentItem()) {
113                 acceptSuggestion();
114                 consumed = true;
115             } else {
116                 lineEdit->event(ev);
117                 hideSuggestions();
118             }
119             break;
120
121         case Qt::Key_Escape:
122             hideSuggestions();
123             consumed = true;
124             break;
125
126         case Qt::Key_Up:
127             if (popup->currentRow() == 0) {
128                 popup->setCurrentItem(0);
129                 popup->clearSelection();
130                 buddy->setText(originalText);
131                 buddy->toWidget()->setFocus();
132                 consumed = true;
133             }
134             break;
135
136         case Qt::Key_Down:
137         case Qt::Key_Home:
138         case Qt::Key_End:
139         case Qt::Key_PageUp:
140         case Qt::Key_PageDown:
141             // qDebug() << key;
142             break;
143
144         default:
145             // qDebug() << keyEvent->text();
146             lineEdit->event(ev);
147             consumed = true;
148             break;
149         }
150
151         return consumed;
152     }
153
154     return false;
155 }
156
157 void AutoComplete::showSuggestions(const QVector<Suggestion *> &suggestions) {
158     if (suggestions.isEmpty()) {
159         hideSuggestions();
160         return;
161     }
162     popup->setUpdatesEnabled(false);
163     popup->clear();
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"));
170     }
171     popup->setCurrentItem(0);
172     int h = popup->frameWidth() * 2;
173     for (int i = 0; i < suggestions.count(); ++i)
174         h += popup->sizeHintForRow(i);
175
176     popup->resize(buddy->toWidget()->width(), h);
177     adjustPosition();
178     popup->setUpdatesEnabled(true);
179
180     if (popup->isHidden()) {
181         itemHovering = false;
182         popup->showNormal();
183         QTimer::singleShot(100, this, SLOT(enableItemHovering()));
184     }
185 }
186
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();
195         hideSuggestions();
196     } else qWarning() << "No suggestion for index" << index;
197 }
198
199 void AutoComplete::preventSuggest() {
200     timer->stop();
201     enabled = false;
202     popup->hide();
203 }
204
205 void AutoComplete::enableSuggest() {
206     enabled = true;
207 }
208
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*>)));
213 }
214
215 void AutoComplete::suggest() {
216     if (!enabled) return;
217
218     popup->setCurrentItem(0);
219     popup->clearSelection();
220
221     originalText = buddy->text();
222     if (originalText.isEmpty()) {
223         hideSuggestions();
224         return;
225     }
226
227     if (suggester) suggester->suggest(originalText);
228 }
229
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);
236 }
237
238 void AutoComplete::adjustPosition() {
239     popup->move(buddy->toWidget()->mapToGlobal(QPoint(0, buddy->toWidget()->height())));
240 }
241
242 void AutoComplete::enableItemHovering() {
243     itemHovering = true;
244 }
245
246 void AutoComplete::hideSuggestions() {
247     itemHovering = false;
248 #ifdef APP_MAC_NO
249     mac::fadeOutWindow(popup);
250 #else
251     popup->hide();
252     popup->clear();
253 #endif
254     if (!originalText.isEmpty()) {
255         buddy->setText(originalText);
256         originalText.clear();
257     }
258     buddy->toWidget()->setFocus();
259     timer->stop();
260 }
261
262 void AutoComplete::itemEntered(QListWidgetItem *item) {
263     if (!itemHovering) return;
264     if (!item) return;
265     item->setSelected(true);
266     popup->setCurrentItem(item);
267 }
268
269 void AutoComplete::currentItemChanged(QListWidgetItem *item) {
270     if (!item) return;
271     buddy->setText(item->text());
272     // lineEdit->setSelection(originalText.length(), lineEdit->text().length());
273 }