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