/* $BEGIN_LICENSE
This file is part of Minitube.
-Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
+Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
Minitube is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
along with Minitube. If not, see <http://www.gnu.org/licenses/>.
$END_LICENSE */
-
#include "autocomplete.h"
#include "suggester.h"
-#ifdef APP_MAC
+#ifdef APP_MAC_SEARCHFIELD
#include "searchlineedit_mac.h"
#else
#include "searchlineedit.h"
#endif
-AutoComplete::AutoComplete(SearchLineEdit *parent, QLineEdit *editor):
- QObject(parent), editor(editor), suggester(0) {
+#ifdef APP_MAC
+#include "macutils.h"
+#endif
- buddy = parent;
- enabled = true;
+#include <QListWidget>
+
+#ifndef QT_NO_DEBUG_OUTPUT
+/// Gives human-readable event type information.
+QDebug operator<<(QDebug str, const QEvent * ev) {
+ static int eventEnumIndex = QEvent::staticMetaObject.indexOfEnumerator("Type");
+ str << "QEvent";
+ if (ev) {
+ QString name = QEvent::staticMetaObject.enumerator(eventEnumIndex).valueToKey(ev->type());
+ if (!name.isEmpty()) str << name; else str << ev->type();
+ } else {
+ str << (void*)ev;
+ }
+ return str.maybeSpace();
+}
+#endif
- popup = new QListWidget;
- popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
- popup->setMouseTracking(true);
- popup->setWindowOpacity(.9);
- popup->installEventFilter(this);
- popup->setWindowFlags(Qt::Popup);
- popup->setFocusPolicy(Qt::NoFocus);
- popup->setFocusProxy(buddy);
+AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit):
+ QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
- connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(doneCompletion()));
+ popup = new QListWidget();
+ popup->setWindowFlags(Qt::Popup);
+ popup->setFocusProxy(buddy->toWidget());
+ popup->installEventFilter(this);
+ buddy->toWidget()->window()->installEventFilter(this);
+ popup->setMouseTracking(true);
- // connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
- // SLOT(currentItemChanged(QListWidgetItem *)));
+ // style
+ popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ popup->setWindowOpacity(.9);
+ popup->setProperty("suggest", true);
- // mouse hover
- // connect(popup, SIGNAL(itemEntered(QListWidgetItem*)),
- // SLOT(currentItemChanged(QListWidgetItem *)));
+ connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(acceptSuggestion()));
+ connect(popup, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
+ SLOT(currentItemChanged(QListWidgetItem*)));
+ connect(popup, SIGNAL(itemEntered(QListWidgetItem*)), SLOT(itemEntered(QListWidgetItem *)));
timer = new QTimer(this);
timer->setSingleShot(true);
- timer->setInterval(600);
- connect(timer, SIGNAL(timeout()), SLOT(autoSuggest()));
- connect(buddy, SIGNAL(textChanged(QString)), timer, SLOT(start()));
-
-}
-
-AutoComplete::~AutoComplete() {
- delete popup;
+ timer->setInterval(500);
+ connect(timer, SIGNAL(timeout()), SLOT(suggest()));
+ connect(buddy->toWidget(), SIGNAL(textEdited(QString)), timer, SLOT(start()));
}
bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
- if (obj != popup)
+
+ if (obj != popup) {
+ switch (ev->type()) {
+ case QEvent::Move:
+ case QEvent::Resize:
+ adjustPosition();
+ break;
+ default:
+ break;
+ }
return false;
+ }
+
+ // qDebug() << ev;
- if (ev->type() == QEvent::FocusOut) {
- popup->hide();
- buddy->setFocus();
+ if (ev->type() == QEvent::Leave) {
+ popup->setCurrentItem(0);
+ popup->clearSelection();
+ if (!originalText.isEmpty()) buddy->setText(originalText);
return true;
}
- if (ev->type() == QEvent::MouseButtonPress) {
- popup->hide();
- buddy->setFocus();
- buddy->setText(originalText);
+ if (ev->type() == QEvent::FocusOut || ev->type() == QEvent::MouseButtonPress) {
+ hideSuggestions();
return true;
}
if (ev->type() == QEvent::KeyPress) {
-
bool consumed = false;
-
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(ev);
- int key = keyEvent->key();
- // qDebug() << keyEvent->text();
- switch (key) {
+ // qWarning() << keyEvent->text();
+ switch (keyEvent->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (popup->currentItem()) {
- doneCompletion();
+ acceptSuggestion();
consumed = true;
} else {
- buddy->setFocus();
- editor->event(ev);
- popup->hide();
+ lineEdit->event(ev);
+ hideSuggestions();
}
break;
case Qt::Key_Escape:
- buddy->setFocus();
- editor->setText(originalText);
- popup->hide();
+ hideSuggestions();
consumed = true;
break;
case Qt::Key_Up:
+ if (popup->currentRow() == 0) {
+ popup->setCurrentItem(0);
+ popup->clearSelection();
+ buddy->setText(originalText);
+ buddy->toWidget()->setFocus();
+ consumed = true;
+ }
+ break;
+
case Qt::Key_Down:
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
+ // qDebug() << key;
break;
default:
// qDebug() << keyEvent->text();
- buddy->setFocus();
- editor->event(ev);
- popup->hide();
+ lineEdit->event(ev);
+ consumed = true;
break;
}
return false;
}
-void AutoComplete::showCompletion(const QStringList &choices) {
-
- if (choices.isEmpty())
+void AutoComplete::showSuggestions(const QList<Suggestion *> &suggestions) {
+ if (suggestions.isEmpty()) {
+ hideSuggestions();
return;
-
+ }
popup->setUpdatesEnabled(false);
popup->clear();
- for (int i = 0; i < choices.count(); ++i) {
- QListWidgetItem * item;
- item = new QListWidgetItem(popup);
- item->setText(choices[i]);
+ for (int i = 0; i < suggestions.count(); ++i) {
+ QListWidgetItem *item = new QListWidgetItem(popup);
+ Suggestion *s = suggestions[i];
+ item->setText(s->value);
+ if (!s->type.isEmpty())
+ item->setIcon(QIcon(":/images/" + s->type + ".png"));
}
popup->setCurrentItem(0);
- popup->adjustSize();
- popup->setUpdatesEnabled(true);
+ int h = popup->frameWidth() * 2;
+ for (int i = 0; i < suggestions.count(); ++i)
+ h += popup->sizeHintForRow(i);
- int h = popup->sizeHintForRow(0) * choices.count() + 4;
- popup->resize(buddy->width(), h);
-
- popup->move(buddy->mapToGlobal(QPoint(0, buddy->height())));
+ popup->resize(buddy->toWidget()->width(), h);
+ adjustPosition();
+ popup->setUpdatesEnabled(true);
- popup->setFocus();
- popup->show();
+ if (popup->isHidden()) {
+ itemHovering = false;
+ popup->showNormal();
+ QTimer::singleShot(100, this, SLOT(enableItemHovering()));
+ }
}
-void AutoComplete::doneCompletion() {
- timer->stop();
- popup->hide();
- buddy->setFocus();
- QListWidgetItem *item = popup->currentItem();
- if (item) {
- buddy->setText(item->text());
- emit suggestionAccepted(item->text());
- }
+void AutoComplete::acceptSuggestion() {
+ int index = popup->currentIndex().row();
+ if (index >= 0 && index < suggestions.size()) {
+ Suggestion* suggestion = suggestions.at(index);
+ buddy->setText(suggestion->value);
+ emit suggestionAccepted(suggestion);
+ emit suggestionAccepted(suggestion->value);
+ originalText.clear();
+ hideSuggestions();
+ } else qWarning() << "No suggestion for index" << index;
}
void AutoComplete::preventSuggest() {
- // qDebug() << "preventSuggest";
timer->stop();
enabled = false;
popup->hide();
}
void AutoComplete::enableSuggest() {
- // qDebug() << "enableSuggest";
enabled = true;
}
void AutoComplete::setSuggester(Suggester* suggester) {
if (this->suggester) this->suggester->disconnect();
this->suggester = suggester;
- connect(suggester, SIGNAL(ready(QStringList)), SLOT(suggestionsReady(QStringList)));
+ connect(suggester, SIGNAL(ready(QList<Suggestion*>)), SLOT(suggestionsReady(QList<Suggestion*>)));
}
-void AutoComplete::autoSuggest() {
+void AutoComplete::suggest() {
if (!enabled) return;
- if (!buddy->hasFocus()) return;
-
- QString query = editor->text();
- originalText = query;
- // qDebug() << "originalText" << originalText;
- if (query.isEmpty()) {
- popup->hide();
- buddy->setFocus();
+
+ popup->setCurrentItem(0);
+ popup->clearSelection();
+
+ originalText = buddy->text();
+ if (originalText.isEmpty()) {
+ hideSuggestions();
return;
}
- if (suggester)
- suggester->suggest(query);
+ if (suggester) suggester->suggest(originalText);
}
-void AutoComplete::suggestionsReady(QStringList suggestions) {
+void AutoComplete::suggestionsReady(const QList<Suggestion *> &suggestions) {
+ qDeleteAll(this->suggestions);
+ this->suggestions = suggestions;
if (!enabled) return;
- showCompletion(suggestions);
+ if (!buddy->toWidget()->hasFocus() && buddy->toWidget()->isVisible()) return;
+ showSuggestions(suggestions);
+}
+
+void AutoComplete::adjustPosition() {
+ popup->move(buddy->toWidget()->mapToGlobal(QPoint(0, buddy->toWidget()->height())));
+}
+
+void AutoComplete::enableItemHovering() {
+ itemHovering = true;
}
-void AutoComplete::currentItemChanged(QListWidgetItem *current) {
- if (current) {
- // qDebug() << "current" << current->text();
- current->setSelected(true);
- buddy->setText(current->text());
- editor->setSelection(originalText.length(), editor->text().length());
+void AutoComplete::hideSuggestions() {
+ itemHovering = false;
+#ifdef APP_MAC_NO
+ mac::fadeOutWindow(popup);
+#else
+ popup->hide();
+ popup->clear();
+#endif
+ if (!originalText.isEmpty()) {
+ buddy->setText(originalText);
+ originalText.clear();
}
+ buddy->toWidget()->setFocus();
+ timer->stop();
+}
+
+void AutoComplete::itemEntered(QListWidgetItem *item) {
+ if (!itemHovering) return;
+ if (!item) return;
+ item->setSelected(true);
+ popup->setCurrentItem(item);
+}
+
+void AutoComplete::currentItemChanged(QListWidgetItem *item) {
+ if (!item) return;
+ buddy->setText(item->text());
+ // lineEdit->setSelection(originalText.length(), lineEdit->text().length());
}