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