]> git.sur5r.net Git - minitube/blobdiff - src/autocomplete.cpp
Upload 3.9.3-2 to unstable
[minitube] / src / autocomplete.cpp
index 1db03ee648084d95578ee4f65fb64730bb8e597f..2c8e5225ee2cbc670cf08b62c97a3ede1132793c 100644 (file)
-#include "autocomplete.h"
-#include "suggester.h"
+/* $BEGIN_LICENSE
 
-AutoComplete::AutoComplete(QWidget *parent, QLineEdit *editor):
-        QObject(parent), buddy(parent), editor(editor), suggester(0) {
-
-    enabled = true;
+This file is part of Minitube.
+Copyright 2013, Flavio Tordini <flavio.tordini@gmail.com>
 
-    popup = new QListWidget;
-    popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    popup->installEventFilter(this);
-    popup->setMouseTracking(true);
-    popup->setWindowOpacity(.9);
+Minitube is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
 
-    connect(popup, SIGNAL(itemClicked(QListWidgetItem*)),
-            SLOT(doneCompletion()));
+Minitube is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-    // connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
-    //    SLOT(currentItemChanged(QListWidgetItem *)));
+You should have received a copy of the GNU General Public License
+along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
 
-    // mouse hover
-    // connect(popup, SIGNAL(itemEntered(QListWidgetItem*)),
-    //    SLOT(currentItemChanged(QListWidgetItem *)));
+$END_LICENSE */
+#include "autocomplete.h"
+#include "suggester.h"
+#ifdef APP_MAC_SEARCHFIELD
+#include "searchlineedit_mac.h"
+#else
+#include "searchlineedit.h"
+#endif
+
+#ifdef APP_MAC
+#include "macutils.h"
+#endif
+
+#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
 
+AutoComplete::AutoComplete(SearchWidget *buddy, QLineEdit *lineEdit)
+    : QObject(lineEdit), buddy(buddy), lineEdit(lineEdit), enabled(true), suggester(0),
+      itemHovering(false) {
+    popup = new QListWidget();
     popup->setWindowFlags(Qt::Popup);
-    popup->setFocusPolicy(Qt::NoFocus);
-    popup->setFocusProxy(parent);
+    popup->setFocusProxy(buddy->toWidget());
+    popup->installEventFilter(this);
+    buddy->toWidget()->window()->installEventFilter(this);
+    popup->setMouseTracking(true);
 
-    timer = new QTimer(this);
-    timer->setSingleShot(true);
-    timer->setInterval(300);
-    connect(timer, SIGNAL(timeout()), SLOT(autoSuggest()));
-    connect(editor, SIGNAL(textEdited(QString)), timer, SLOT(start()));
+    // style
+    popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    popup->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    popup->setWindowOpacity(.9);
+    popup->setProperty("suggest", true);
 
-}
+    connect(popup, SIGNAL(itemClicked(QListWidgetItem *)), SLOT(acceptSuggestion()));
+    connect(popup, SIGNAL(currentItemChanged(QListWidgetItem *, QListWidgetItem *)),
+            SLOT(currentItemChanged(QListWidgetItem *)));
+    connect(popup, SIGNAL(itemEntered(QListWidgetItem *)), SLOT(itemEntered(QListWidgetItem *)));
 
-AutoComplete::~AutoComplete() {
-    delete popup;
+    timer = new QTimer(this);
+    timer->setSingleShot(true);
+    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;
+    }
 
-    if (ev->type() == QEvent::MouseButtonPress) {
-        popup->hide();
-        editor->setFocus();
-        editor->setText(originalText);
+    // qDebug() << ev;
+
+    if (ev->type() == QEvent::Leave) {
+        popup->setCurrentItem(0);
+        popup->clearSelection();
+        if (!originalText.isEmpty()) buddy->setText(originalText);
         return true;
     }
 
-    if (ev->type() == QEvent::KeyPress) {
+    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) {
+        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
+        // qWarning() << keyEvent->text();
+        switch (keyEvent->key()) {
         case Qt::Key_Enter:
         case Qt::Key_Return:
             if (popup->currentItem()) {
-                doneCompletion();
+                acceptSuggestion();
                 consumed = true;
             } else {
-                editor->setFocus();
-                editor->event(ev);
-                popup->hide();
+                lineEdit->event(ev);
+                hideSuggestions();
             }
             break;
 
         case Qt::Key_Escape:
-            editor->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();
-            editor->setFocus();
-            editor->event(ev);
-            popup->hide();
+            lineEdit->event(ev);
+            consumed = true;
             break;
         }
 
@@ -98,86 +156,121 @@ bool AutoComplete::eventFilter(QObject *obj, QEvent *ev) {
     return false;
 }
 
-void AutoComplete::showCompletion(const QStringList &choices) {
-
-    if (choices.isEmpty())
+void AutoComplete::showSuggestions(const QVector<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);
+    popup->setCurrentItem(nullptr);
+    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();
-    editor->setFocus();
-    QListWidgetItem *item = popup->currentItem();
-    if (item) {
-        editor->setText(item->text());
-        QKeyEvent *e;
-        e = new QKeyEvent(QEvent::KeyPress, Qt::Key_Enter, Qt::NoModifier);
-        QApplication::postEvent(editor, e);
-        e = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Enter, Qt::NoModifier);
-        QApplication::postEvent(editor, e);
-    }
+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(Suggestersuggester) {
+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(QVector<Suggestion *>)),
+            SLOT(suggestionsReady(QVector<Suggestion *>)));
 }
 
-void AutoComplete::autoSuggest() {
+void AutoComplete::suggest() {
     if (!enabled) return;
 
-    QString query = editor->text();
-    originalText = query;
-    // qDebug() << "originalText" << originalText;
-    if (query.isEmpty()) return;
+    popup->setCurrentItem(nullptr);
+    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 QVector<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);
-        editor->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());
 }