]> git.sur5r.net Git - minitube/blob - src/autocomplete.cpp
0a78c9cc96d0645d9cbeed2ca6b24b6c2f57134f
[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 QList<Suggestion *> &suggestions) {
158     qDebug() << __PRETTY_FUNCTION__;
159     if (suggestions.isEmpty()) {
160         hideSuggestions();
161         return;
162     }
163     popup->setUpdatesEnabled(false);
164     popup->clear();
165     for (int i = 0; i < suggestions.count(); ++i) {
166         QListWidgetItem *item = new QListWidgetItem(popup);
167         Suggestion *s = suggestions[i];
168         item->setText(s->value);
169         if (!s->type.isEmpty())
170             item->setIcon(QIcon(":/images/" + s->type + ".png"));
171     }
172     popup->setCurrentItem(0);
173     int h = popup->frameWidth() * 2;
174     for (int i = 0; i < suggestions.count(); ++i)
175         h += popup->sizeHintForRow(i);
176
177     popup->resize(buddy->toWidget()->width(), h);
178     adjustPosition();
179     popup->setUpdatesEnabled(true);
180
181     if (popup->isHidden()) {
182         itemHovering = false;
183         popup->showNormal();
184         QTimer::singleShot(100, this, SLOT(enableItemHovering()));
185     }
186 }
187
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();
196         hideSuggestions();
197     } else qWarning() << "No suggestion for index" << index;
198 }
199
200 void AutoComplete::preventSuggest() {
201     timer->stop();
202     enabled = false;
203     popup->hide();
204 }
205
206 void AutoComplete::enableSuggest() {
207     enabled = true;
208 }
209
210 void AutoComplete::setSuggester(Suggester* suggester) {
211     if (this->suggester) this->suggester->disconnect();
212     this->suggester = suggester;
213     connect(suggester, SIGNAL(ready(QList<Suggestion*>)), SLOT(suggestionsReady(QList<Suggestion*>)));
214 }
215
216 void AutoComplete::suggest() {
217     if (!enabled) return;
218
219     popup->setCurrentItem(0);
220     popup->clearSelection();
221
222     originalText = buddy->text();
223     if (originalText.isEmpty()) {
224         hideSuggestions();
225         return;
226     }
227
228     if (suggester) suggester->suggest(originalText);
229 }
230
231 void AutoComplete::suggestionsReady(const QList<Suggestion *> &suggestions) {
232     qDeleteAll(this->suggestions);
233     this->suggestions = suggestions;
234     if (!enabled) return;
235     if (!buddy->toWidget()->hasFocus() && buddy->toWidget()->isVisible()) return;
236     showSuggestions(suggestions);
237 }
238
239 void AutoComplete::adjustPosition() {
240     popup->move(buddy->toWidget()->mapToGlobal(QPoint(0, buddy->toWidget()->height())));
241 }
242
243 void AutoComplete::enableItemHovering() {
244     itemHovering = true;
245 }
246
247 void AutoComplete::hideSuggestions() {
248     itemHovering = false;
249 #ifdef APP_MAC_NO
250     mac::fadeOutWindow(popup);
251 #else
252     popup->hide();
253     popup->clear();
254 #endif
255     if (!originalText.isEmpty()) {
256         buddy->setText(originalText);
257         originalText.clear();
258     }
259     buddy->toWidget()->setFocus();
260     timer->stop();
261 }
262
263 void AutoComplete::itemEntered(QListWidgetItem *item) {
264     if (!itemHovering) return;
265     if (!item) return;
266     item->setSelected(true);
267     popup->setCurrentItem(item);
268 }
269
270 void AutoComplete::currentItemChanged(QListWidgetItem *item) {
271     if (!item) return;
272     buddy->setText(item->text());
273     // lineEdit->setSelection(originalText.length(), lineEdit->text().length());
274 }