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