]> git.sur5r.net Git - minitube/blobdiff - src/autocomplete.cpp
Imported Upstream version 2.3
[minitube] / src / autocomplete.cpp
index f91540892d972d5266c323821b4c555f080fbb3b..badfd708092f685192ffd46f4b66e572a44dde98 100644 (file)
@@ -1,7 +1,7 @@
 /* $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
@@ -17,108 +17,129 @@ You should have received a copy of the GNU General Public License
 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
 
 $END_LICENSE */
-
 #include "autocomplete.h"
 #include "suggester.h"
 #ifdef APP_MAC
 #include "searchlineedit_mac.h"
+#include "macutils.h"
 #else
 #include "searchlineedit.h"
 #endif
 
-AutoComplete::AutoComplete(SearchLineEdit *parent, QLineEdit *editor):
-    QObject(parent), editor(editor), suggester(0) {
+#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
 
-    buddy = parent;
-    enabled = true;
+AutoComplete::AutoComplete(SearchLineEdit *buddy, QLineEdit *lineEdit):
+    QObject(buddy), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0), itemHovering(false) {
 
-    popup = new QListWidget;
-    popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    popup->setMouseTracking(true);
-    popup->setWindowOpacity(.9);
-    popup->installEventFilter(this);
+    popup = new QListWidget();
     popup->setWindowFlags(Qt::Popup);
-    popup->setFocusPolicy(Qt::NoFocus);
     popup->setFocusProxy(buddy);
+    popup->installEventFilter(this);
+    buddy->window()->installEventFilter(this);
+    popup->setMouseTracking(true);
 
-    connect(popup, SIGNAL(itemClicked(QListWidgetItem*)), SLOT(doneCompletion()));
-
-    // 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, 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->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;
         }
 
@@ -128,87 +149,120 @@ bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
     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);
+    adjustPosition();
+    popup->setUpdatesEnabled(true);
 
-    popup->move(buddy->mapToGlobal(QPoint(0, buddy->height())));
-
-    popup->setFocus();
-    popup->show();
+    if (popup->isHidden()) {
+        itemHovering = false;
+        popup->show();
+        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->hasFocus()) return;
+    showSuggestions(suggestions);
 }
 
-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::adjustPosition() {
+    popup->move(buddy->mapToGlobal(QPoint(0, buddy->height())));
+}
+
+void AutoComplete::enableItemHovering() {
+    itemHovering = true;
+}
+
+void AutoComplete::hideSuggestions() {
+    itemHovering = false;
+#ifdef APP_MAC
+    mac::fadeOutWindow(popup);
+#else
+    popup->hide();
+    popup->clear();
+#endif
+    if (!originalText.isEmpty()) {
+        buddy->setText(originalText);
+        originalText.clear();
     }
+    buddy->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());
 }