frontend chat input history, use arrow keys UP/DOWN
authorsheepluva
Wed, 19 Oct 2011 02:10:27 +0200
changeset 6150 1d98752c1fba
parent 6149 0b92341adb6a
child 6151 9fd5b70acb1a
frontend chat input history, use arrow keys UP/DOWN
QTfrontend/ui/widget/SmartLineEdit.cpp
QTfrontend/ui/widget/SmartLineEdit.h
QTfrontend/ui/widget/chatwidget.cpp
--- a/QTfrontend/ui/widget/SmartLineEdit.cpp	Tue Oct 18 15:34:40 2011 +0200
+++ b/QTfrontend/ui/widget/SmartLineEdit.cpp	Wed Oct 19 02:10:27 2011 +0200
@@ -21,14 +21,19 @@
 
 #include "SmartLineEdit.h"
 
-SmartLineEdit::SmartLineEdit(QWidget * parent)
+SmartLineEdit::SmartLineEdit(QWidget * parent, int maxHistorySize)
 : QLineEdit(parent)
 {
+    m_curHistEntryIdx = 0;
+    m_maxHistorySize = maxHistorySize;
+
     m_whitespace = QRegExp("\\s");
 
     m_cmds  = new QStringList();
     m_nicks = new QStringList();
 
+    m_history = new QStringList();
+
     resetAutoCompletionStatus();
 
     // reset autocompletion status when cursor is moved or content is changed
@@ -41,58 +46,140 @@
 
 void SmartLineEdit::addCommands(const QStringList & commands)
 {
-    m_mutex.lock();
+    m_keywordMutex.lock();
 
     m_cmds->append(commands);
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
 }
 
 
 void SmartLineEdit::removeCommands(const QStringList & commands)
 {
-    m_mutex.lock();
+    m_keywordMutex.lock();
 
     foreach (const QString & cmd, commands)
     {
         m_cmds->removeAll(cmd);
     }
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
 }
 
 
 void SmartLineEdit::addNickname(const QString & name)
 {
-    m_mutex.lock();
+    m_keywordMutex.lock();
 
     m_nicks->append(name);
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
 }
 
 
 void SmartLineEdit::removeNickname(const QString & name)
 {
-    m_mutex.lock();
+    m_keywordMutex.lock();
 
     m_nicks->removeAll(name);
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
+}
+
+void SmartLineEdit::rememberCurrentText()
+{
+    m_historyMutex.lock();
+
+    rememberCurrentTextUnsynced();
+
+    m_historyMutex.unlock();
+}
+
+void SmartLineEdit::rememberCurrentTextUnsynced()
+{
+    QString newEntry = text();
+
+    // don't store whitespace-only/empty text
+    if (newEntry.trimmed().isEmpty())
+        return;
+
+    m_history->removeOne(newEntry); // no duplicates please
+    m_history->append(newEntry);
+
+    // do not keep more entries than allowed
+    if (m_history->size() > m_maxHistorySize)
+        m_history->removeFirst();
+
+    // we're looking at the latest entry
+    m_curHistEntryIdx = m_history->size() - 1;
+}
+
+void SmartLineEdit::clear()
+{
+    m_historyMutex.lock();
+
+    QLineEdit::clear();
+    m_curHistEntryIdx = m_history->size();
+
+    m_historyMutex.unlock();
 }
 
 void SmartLineEdit::forgetEverything()
 {
-    m_mutex.lock();
+    // forget keywords
+    m_keywordMutex.lock();
 
     m_cmds->clear();
     m_nicks->clear();
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
+
+    // forget history
+    m_historyMutex.lock();
+
+    m_history->clear();
+    m_curHistEntryIdx = 0;
+
+    m_historyMutex.unlock();
 
     resetAutoCompletionStatus();
 }
 
+void SmartLineEdit::navigateHistory(bool isGoingUp)
+{
+    m_historyMutex.lock();
+
+    // save possible changes to new entry
+    if ((m_curHistEntryIdx >= m_history->size() ||
+        (text() != m_history->at(m_curHistEntryIdx))))
+        {
+            rememberCurrentTextUnsynced();
+        }
+
+    if (isGoingUp)
+        m_curHistEntryIdx--;
+    else
+        m_curHistEntryIdx++;
+
+    // if Idx higher than valid range
+    if (m_curHistEntryIdx >= m_history->size())
+    {
+        QLineEdit::clear();
+        m_curHistEntryIdx = m_history->size();
+    }
+    // if Idx in valid range
+    else if (m_curHistEntryIdx >= 0)
+    {
+        setText(m_history->at(m_curHistEntryIdx));
+    }
+    // if Idx below 0
+    else
+        m_curHistEntryIdx = 0;
+
+
+    m_historyMutex.unlock();
+}
+
 bool SmartLineEdit::event(QEvent * event)
 {
     // we only want special treatment for key press events
@@ -121,11 +208,29 @@
         autoComplete();
         event->accept();
     }
-    // clear contents on pressed ESC
-    else if ((event->key() == Qt::Key_Escape) &&
-             (event->modifiers() == Qt::NoModifier)
-    )
-        clear();
+    // clear contents on pressed ESC, navigate history with arrow keys
+    else if (event->modifiers() == Qt::NoModifier)
+        switch (key)
+        {
+            case Qt::Key_Escape:
+                clear();
+                event->accept();
+                break;
+
+            case Qt::Key_Up:
+                navigateHistory(true);
+                event->accept();
+                break;
+
+            case Qt::Key_Down:
+                navigateHistory(false);
+                event->accept();
+                break;
+
+            default:
+                QLineEdit::keyPressEvent(event);
+                break;
+        }
     // otherwise forward keys to parent
     else
         QLineEdit::keyPressEvent(event);
@@ -152,10 +257,10 @@
     }
     else
     {
-        m_mutex.lock();
+        m_keywordMutex.lock();
         m_cmds->sort();
         m_nicks->sort();
-        m_mutex.unlock();
+        m_keywordMutex.unlock();
 
         int cp = cursorPosition();
 
@@ -190,7 +295,7 @@
     }
 
 
-    m_mutex.lock();
+    m_keywordMutex.lock();
 
     if (isFirstWord)
     {
@@ -229,7 +334,7 @@
         }
     }
 
-    m_mutex.unlock();
+    m_keywordMutex.unlock();
 
     // we found a single match?
     if (!match.isEmpty())
--- a/QTfrontend/ui/widget/SmartLineEdit.h	Tue Oct 18 15:34:40 2011 +0200
+++ b/QTfrontend/ui/widget/SmartLineEdit.h	Wed Oct 19 02:10:27 2011 +0200
@@ -33,11 +33,15 @@
 class QLineEdit;
 
 /**
- * A modification of QLineEdit that will attempt to auto-complete the current
- * word with cursor when the TAB key is pressed.
- * Additionally it will delete its contents when ESC is pressed.
- * A Keyword can either be a command (if first word) or
- * a nickname (completed if any word)
+ * A modification of QLineEdit that features:
+ * + Auto-completion for word under cursor when the TAB key is pressed.
+ * + ESC key clears text.
+ * + History of previous contents, re-selectable using the arrow keys.
+ *
+ * Note:
+ *   * A Keyword can either be a command (if first word) or
+ *     a nickname (completed regardless of position in text).
+ *   * Public methods for accessing keywords and history are thread-safe.
  * @author sheepluva
  * @since 0.9.17
  */
@@ -48,8 +52,10 @@
 public:
     /**
     * Class constructor.
+    * @param parent parent QWidget.
+    * @param maxHistorySize maximum amount of history entries kept.
     */
-    SmartLineEdit(QWidget * parent = 0);
+    SmartLineEdit(QWidget * parent = 0, int maxHistorySize = 64);
 
     /**
      * Adds commands to the auto-completion feature.
@@ -64,6 +70,11 @@
     void addNickname(const QString & nickname);
 
     /**
+     * Appends current text to history.
+     */
+    void rememberCurrentText();
+
+    /**
      * Removes commands from the auto-completion feature.
      * @param commands list of commands to be removed.
      */
@@ -81,6 +92,13 @@
     void forgetEverything();
 
 
+public slots:
+    /**
+     * Clears the contents.
+     */
+    void clear();
+
+
 protected:
     /**
      * Overrides method of parent class.
@@ -94,9 +112,10 @@
     /**
      * Overrides method of parent class.
      * Autocompletes if TAB is reported as pressed key in the key event,
-     * otherwise keys except for ESC (with no modifiers)
+     * otherwise keys except for ESC and Up/Down (with no modifiers)
      * are forwarded to parent method.
      * ESC leads to the contents being cleared.
+     * Arrow keys are used for navigating the history.
      * @param event the key event.
      */
     virtual void keyPressEvent(QKeyEvent * event);
@@ -105,9 +124,14 @@
 private:
     QRegExp m_whitespace; // regexp that matches a whitespace
 
+    int m_maxHistorySize; // the maximum allowed size for the history
+    int m_curHistEntryIdx; // the index of the currently used entry or -1
+
     QStringList * m_cmds;  // list of recognized commands
     QStringList * m_nicks; // list of recognized nicknames
 
+    QStringList * m_history; // history of previous inputs
+
     // these variables contain information about the last replacement
     // they get reset whenever cursor is moved or text is changed
 
@@ -116,13 +140,27 @@
     QString m_prefix; // prefix of the text replacement this widget just did
     QString m_postfix; // postfix of the text replacement this widget just did
 
-    QMutex m_mutex; // make all the QStringList action thread-safe
+    QMutex m_keywordMutex; // make keyword QStringList action thread-safe
+    QMutex m_historyMutex; // make history QStringList action thread-safe
 
     /**
      * Autocompletes the contents based on the known commands and/or names.
      */
     void autoComplete();
 
+    /**
+     * Navigates content history in the desired direction.
+     * Note: no wrap-around on purpose (so that holding down/up will get the
+     * the user to the respective end rather than into an endless cycle :P)
+     * @param isGoingUp true: next older entry, false: next more recent entry.
+     */
+    void navigateHistory(bool isGoingUp);
+
+    /**
+     * Appends current text to history, without Mutex.
+     */
+    void rememberCurrentTextUnsynced();
+
 
 private slots:
     /**
--- a/QTfrontend/ui/widget/chatwidget.cpp	Tue Oct 18 15:34:40 2011 +0200
+++ b/QTfrontend/ui/widget/chatwidget.cpp	Wed Oct 19 02:10:27 2011 +0200
@@ -334,6 +334,7 @@
 void HWChatWidget::returnPressed()
 {
     QStringList lines = chatEditLine->text().split('\n');
+    chatEditLine->rememberCurrentText();
     chatEditLine->clear();
     foreach (const QString &line, lines)
         emit chatLine(line);