From d55c1345449a34adb3192c23fe3760bd0aae645b Mon Sep 17 00:00:00 2001
From: Jared Adams <jaxad0127@gmail.com>
Date: Sun, 29 Aug 2010 09:01:27 -0600
Subject: Move handling of autocomplete and input history into TextField

Reviewed-by: Freeyorp
---
 src/beingmanager.cpp          |  61 +++++++++++----
 src/beingmanager.h            |  12 ++-
 src/gui/chat.cpp              | 178 ++++--------------------------------------
 src/gui/chat.h                |  25 ++----
 src/gui/textdialog.cpp        |   4 +-
 src/gui/widgets/chattab.cpp   |   6 ++
 src/gui/widgets/chattab.h     |   7 +-
 src/gui/widgets/textfield.cpp | 137 +++++++++++++++++++++++++++++---
 src/gui/widgets/textfield.h   |  68 ++++++++++++++--
 src/utils/stringutils.cpp     |  37 ++++++++-
 src/utils/stringutils.h       |   4 +
 11 files changed, 318 insertions(+), 221 deletions(-)

diff --git a/src/beingmanager.cpp b/src/beingmanager.cpp
index 931e4579..d7045684 100644
--- a/src/beingmanager.cpp
+++ b/src/beingmanager.cpp
@@ -53,8 +53,48 @@ class FindBeingFunctor
         Being::Type type;
 } beingFinder;
 
+class PlayerNamesLister : public AutoCompleteLister
+{
+    void getAutoCompleteList(std::vector<std::string>& names) const
+    {
+        Beings::iterator i = beingManager->mBeings.begin();
+        names.clear();
+
+        while (i != beingManager->mBeings.end())
+        {
+            Being *being = (*i);
+            if (being->getType() == Being::PLAYER && being->getName() != "")
+                names.push_back(being->getName());
+
+            ++i;
+        }
+    }
+};
+
+class PlayerNPCNamesLister : public AutoCompleteLister
+{
+    void getAutoCompleteList(std::vector<std::string>& names) const
+    {
+        Beings::iterator i = beingManager->mBeings.begin();
+        names.clear();
+
+        while (i != beingManager->mBeings.end())
+        {
+            Being *being = (*i);
+            if ((being->getType() == Being::PLAYER
+                 || being->getType() == Being::NPC)
+                && being->getName() != "")
+                names.push_back(being->getName());
+
+            ++i;
+        }
+    }
+};
+
 BeingManager::BeingManager()
 {
+    mPlayerNames = new PlayerNamesLister;
+    mPlayerNPCNames = new PlayerNPCNamesLister;
 }
 
 BeingManager::~BeingManager()
@@ -263,23 +303,14 @@ bool BeingManager::hasBeing(Being *being) const
     return false;
 }
 
-void BeingManager::getPlayerNames(std::vector<std::string> &names,
-                                  bool npcNames)
+AutoCompleteLister *BeingManager::getPlayerNameLister()
 {
-    Beings::iterator i = mBeings.begin();
-    names.clear();
+    return mPlayerNames;
+}
 
-    while (i != mBeings.end())
-    {
-        Being *being = (*i);
-        if ((being->getType() == Being::PLAYER
-             || (being->getType() == Being::NPC && npcNames))
-            && being->getName() != "")
-        {
-            names.push_back(being->getName());
-        }
-        ++i;
-    }
+AutoCompleteLister *BeingManager::getPlayerNPCNameLister()
+{
+    return mPlayerNPCNames;
 }
 
 void BeingManager::updatePlayerNames()
diff --git a/src/beingmanager.h b/src/beingmanager.h
index 7fd63afe..f2f8eb6d 100644
--- a/src/beingmanager.h
+++ b/src/beingmanager.h
@@ -24,6 +24,8 @@
 
 #include "being.h"
 
+#include "gui/widgets/textfield.h"
+
 class LocalPlayer;
 class Map;
 
@@ -122,12 +124,18 @@ class BeingManager
          */
         void clear();
 
-        void getPlayerNames(std::vector<std::string> &names,
-                            bool npcNames);
+        AutoCompleteLister *getPlayerNameLister();
+
+        AutoCompleteLister *getPlayerNPCNameLister();
 
         void updatePlayerNames();
 
     protected:
+        friend class PlayerNamesLister;
+        friend class PlayerNPCNamesLister;
+
+        AutoCompleteLister *mPlayerNames;
+        AutoCompleteLister *mPlayerNPCNames;
         Beings mBeings;
         Map *mMap;
 };
diff --git a/src/gui/chat.cpp b/src/gui/chat.cpp
index f87a8a3d..6d900e98 100644
--- a/src/gui/chat.cpp
+++ b/src/gui/chat.cpp
@@ -72,9 +72,21 @@ class ChatInput : public TextField, public gcn::FocusListener
         }
 };
 
+class ChatAutoComplete : public AutoCompleteLister
+{
+    void getAutoCompleteList(std::vector<std::string> &list) const
+    {
+        ChatTab *tab = static_cast<ChatTab*>(chatWindow->mChatTabs
+                                             ->getSelectedTab());
+
+        return tab->getAutoCompleteList(list);
+    }
+};
 
 ChatWindow::ChatWindow():
     Window(_("Chat")),
+    mHistory(new TextHistory()),
+    mAutoComplete(new ChatAutoComplete),
     mTmpVisible(false)
 {
     setWindowName("Chat");
@@ -104,9 +116,8 @@ ChatWindow::ChatWindow():
 
     loadWindowState();
 
-    // Add key listener to chat input to be able to respond to up/down
-    mChatInput->addKeyListener(this);
-    mCurHist = mHistory.end();
+    mChatInput->setHistory(mHistory);
+    mChatInput->setAutoComplete(mAutoComplete);
 
     mReturnToggles = config.getValue("ReturnToggles", "0") == "1";
 
@@ -119,6 +130,8 @@ ChatWindow::~ChatWindow()
     delete mRecorder;
     removeAllWhispers();
     delete mItemLinkHandler;
+    delete mHistory;
+    delete mAutoComplete;
 }
 
 void ChatWindow::resetToDefaultSize()
@@ -173,14 +186,6 @@ void ChatWindow::action(const gcn::ActionEvent &event)
 
         if (!message.empty())
         {
-            // If message different from previous, put it in the history
-            if (mHistory.empty() || message != mHistory.back())
-            {
-                mHistory.push_back(message);
-            }
-            // Reset history iterator
-            mCurHist = mHistory.end();
-
             // Send the message to the server
             chatInput(message);
 
@@ -366,48 +371,6 @@ void ChatWindow::mouseDragged(gcn::MouseEvent &event)
     }
 }
 
-
-void ChatWindow::keyPressed(gcn::KeyEvent &event)
-{
-    if (event.getKey().getValue() == Key::DOWN)
-    {
-        if (mCurHist != mHistory.end())
-        {
-            // Move forward through the history
-            HistoryIterator prevHist = mCurHist++;
-
-            if (mCurHist != mHistory.end())
-            {
-                mChatInput->setText(*mCurHist);
-                mChatInput->setCaretPosition(mChatInput->getText().length());
-            }
-            else
-            {
-                mChatInput->setText("");
-                mCurHist = prevHist;
-            }
-        }
-        else if (mChatInput->getText() != "")
-        {
-            mChatInput->setText("");
-        }
-    }
-    else if (event.getKey().getValue() == Key::UP &&
-            mCurHist != mHistory.begin() && mHistory.size() > 0)
-    {
-        // Move backward through the history
-        mCurHist--;
-        mChatInput->setText(*mCurHist);
-        mChatInput->setCaretPosition(mChatInput->getText().length());
-    }
-    else if (event.getKey().getValue() == Key::TAB &&
-             mChatInput->getText() != "")
-    {
-        autoComplete();
-        return;
-    }
-}
-
 void ChatWindow::addInputText(const std::string &text)
 {
     const int caretPos = mChatInput->getCaretPosition();
@@ -516,112 +479,3 @@ ChatTab *ChatWindow::addWhisperTab(const std::string &nick, bool switchTo)
 
     return ret;
 }
-
-void ChatWindow::autoComplete()
-{
-    int caretPos = mChatInput->getCaretPosition();
-    int startName = 0;
-    const std::string inputText = mChatInput->getText();
-    std::string name = inputText.substr(0, caretPos);
-    std::string newName("");
-
-    for (int f = caretPos - 1; f > -1; f --)
-    {
-        if (isWordSeparator(inputText[f]))
-        {
-            startName = f + 1;
-            name = inputText.substr(f + 1, caretPos - f);
-            break;
-        }
-    }
-
-    if (caretPos - 1 + 1 == startName)
-        return;
-
-
-    ChatTab *cTab = static_cast<ChatTab*>(mChatTabs->getSelectedTab());
-    std::vector<std::string> nameList;
-    cTab->getAutoCompleteList(nameList);
-    newName = autoComplete(nameList, name);
-
-    if (newName == "")
-    {
-        beingManager->getPlayerNames(nameList, true);
-        newName = autoComplete(nameList, name);
-    }
-    if (newName == "")
-    {
-        newName = autoCompleteHistory(name);
-    }
-
-    if (newName != "")
-    {
-        if(inputText[0] == '@' || inputText[0] == '/')
-            newName = "\"" + newName + "\"";
-
-        mChatInput->setText(inputText.substr(0, startName) + newName
-                            + inputText.substr(caretPos, inputText.length() - caretPos));
-
-        if (startName > 0)
-            mChatInput->setCaretPosition(caretPos - name.length() + newName.length() + 1);
-        else
-            mChatInput->setCaretPosition(caretPos - name.length() + newName.length());
-    }
-}
-
-std::string ChatWindow::autoComplete(std::vector<std::string> &names,
-                                     std::string partName) const
-{
-    std::vector<std::string>::iterator i = names.begin();
-    toLower(partName);
-    std::string newName("");
-
-    while (i != names.end())
-    {
-        if (!i->empty())
-        {
-            std::string name = *i;
-            toLower(name);
-
-            std::string::size_type pos = name.find(partName, 0);
-            if (pos == 0)
-            {
-                if (newName != "")
-                {
-                    toLower(newName);
-                    newName = findSameSubstring(name, newName);
-                }
-                else
-                {
-                    newName = *i;
-                }
-            }
-        }
-        ++i;
-    }
-
-    return newName;
-}
-
-std::string ChatWindow::autoCompleteHistory(std::string partName)
-{
-    History::iterator i = mHistory.begin();
-    std::vector<std::string> nameList;
-
-    while (i != mHistory.end())
-    {
-        std::string line = *i;
-        unsigned int f = 0;
-        while (f < line.length() && !isWordSeparator(line.at(f)))
-        {
-            f++;
-        }
-        line = line.substr(0, f);
-        if (line != "")
-        {
-            nameList.push_back(line);
-        }
-        ++i;
-    }
-    return autoComplete(nameList, partName);
-}
diff --git a/src/gui/chat.h b/src/gui/chat.h
index b0d91556..1c673556 100644
--- a/src/gui/chat.h
+++ b/src/gui/chat.h
@@ -23,6 +23,7 @@
 #define CHAT_H
 
 #include "gui/widgets/window.h"
+#include "gui/widgets/textfield.h"
 
 #include <guichan/actionlistener.hpp>
 #include <guichan/keylistener.hpp>
@@ -61,8 +62,7 @@ struct CHATLOG
  * \ingroup Interface
  */
 class ChatWindow : public Window,
-                   public gcn::ActionListener,
-                   public gcn::KeyListener
+                   public gcn::ActionListener
 {
     public:
         /**
@@ -131,9 +131,6 @@ class ChatWindow : public Window,
          */
         void chatInput(const std::string &msg);
 
-        /** Called when key is pressed */
-        void keyPressed(gcn::KeyEvent &event);
-
         /** Add the given text to the chat input. */
         void addInputText(const std::string &text);
 
@@ -143,11 +140,9 @@ class ChatWindow : public Window,
         /** Override to reset mTmpVisible */
         void setVisible(bool visible);
 
-
 	void mousePressed(gcn::MouseEvent &event);
 	void mouseDragged(gcn::MouseEvent &event);
 
-
         /**
          * Scrolls the chat window
          *
@@ -177,7 +172,7 @@ class ChatWindow : public Window,
     protected:
         friend class ChatTab;
         friend class WhisperTab;
-        friend class TextField;
+        friend class ChatAutoComplete;
 
         /** Remove the given tab from the window */
         void removeTab(ChatTab *tab);
@@ -189,13 +184,6 @@ class ChatWindow : public Window,
 
         void removeAllWhispers();
 
-        void autoComplete();
-
-        std::string autoCompleteHistory(std::string partName);
-
-        std::string autoComplete(std::vector<std::string> &names,
-                                 std::string partName) const;
-
         /** Used for showing item popup on clicking links **/
         ItemLinkHandler *mItemLinkHandler;
         Recorder *mRecorder;
@@ -203,6 +191,9 @@ class ChatWindow : public Window,
         /** Input box for typing chat messages. */
         ChatInput *mChatInput;
 
+        TextHistory *mHistory;
+        AutoCompleteLister *mAutoComplete;
+
     private:
         bool mTmpVisible;
 
@@ -213,10 +204,6 @@ class ChatWindow : public Window,
         /** Manage whisper tabs */
         TabMap mWhispers;
 
-        typedef std::list<std::string> History;
-        typedef History::iterator HistoryIterator;
-        History mHistory;           /**< Command history. */
-        HistoryIterator mCurHist;   /**< History iterator. */
         bool mReturnToggles; /**< Marks whether <Return> toggles the chat log
                                 or not */
 };
diff --git a/src/gui/textdialog.cpp b/src/gui/textdialog.cpp
index 28792a0b..d9728357 100644
--- a/src/gui/textdialog.cpp
+++ b/src/gui/textdialog.cpp
@@ -21,6 +21,8 @@
 
 #include "gui/textdialog.h"
 
+#include "beingmanager.h"
+
 #include "gui/widgets/button.h"
 #include "gui/widgets/label.h"
 #include "gui/widgets/textfield.h"
@@ -40,7 +42,7 @@ TextDialog::TextDialog(const std::string &title, const std::string &msg,
     // In TextField the escape key will either cause autoComplete or lose focus
     mTextField = new TextField("", ! autoCompleteEnabled);
     if (autoCompleteEnabled)
-        mTextField->setAutoComplete(true);
+        mTextField->setAutoComplete(beingManager->getPlayerNameLister());
 
     mTextField->addActionListener(this);
 
diff --git a/src/gui/widgets/chattab.cpp b/src/gui/widgets/chattab.cpp
index 39ea6887..8bf57508 100644
--- a/src/gui/widgets/chattab.cpp
+++ b/src/gui/widgets/chattab.cpp
@@ -21,6 +21,7 @@
 
 #include "gui/widgets/chattab.h"
 
+#include "beingmanager.h"
 #include "commandhandler.h"
 #include "configuration.h"
 #include "localplayer.h"
@@ -275,6 +276,11 @@ void ChatTab::handleCommand(const std::string &msg)
     commandHandler->handleCommand(msg, this);
 }
 
+void ChatTab::getAutoCompleteList(std::vector<std::string> &names) const
+{
+    beingManager->getPlayerNPCNameLister()->getAutoCompleteList(names);
+}
+
 void ChatTab::addRow(std::string &line)
 {
     std::string::size_type idx = 0;
diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h
index 7fd3931e..c2dfa1c1 100644
--- a/src/gui/widgets/chattab.h
+++ b/src/gui/widgets/chattab.h
@@ -25,6 +25,7 @@
 #include "gui/chat.h"
 
 #include "gui/widgets/tab.h"
+#include "gui/widgets/textfield.h"
 
 class BrowserBox;
 class Recorder;
@@ -45,7 +46,7 @@ enum
 /**
  * A tab for the chat window. This is special to ease chat handling.
  */
-class ChatTab : public Tab
+class ChatTab : public Tab, public AutoCompleteLister
 {
     public:
         /**
@@ -111,6 +112,8 @@ class ChatTab : public Tab
                                    const std::string &args)
         { return false; }
 
+        void getAutoCompleteList(std::vector<std::string> &names) const;
+
     protected:
         friend class ChatWindow;
         friend class WhisperWindow;
@@ -121,8 +124,6 @@ class ChatTab : public Tab
 
         virtual void handleCommand(const std::string &msg);
 
-        virtual void getAutoCompleteList(std::vector<std::string>&) const {}
-
         void addRow(std::string &line);
 
         ScrollArea *mScrollArea;
diff --git a/src/gui/widgets/textfield.cpp b/src/gui/widgets/textfield.cpp
index 60a1f57f..3e02be98 100644
--- a/src/gui/widgets/textfield.cpp
+++ b/src/gui/widgets/textfield.cpp
@@ -25,7 +25,6 @@
 #include "configuration.h"
 #include "graphics.h"
 
-#include "gui/chat.h"
 #include "gui/palette.h"
 #include "gui/sdlinput.h"
 #include "gui/theme.h"
@@ -34,6 +33,7 @@
 
 #include "utils/copynpaste.h"
 #include "utils/dtor.h"
+#include "utils/stringutils.h"
 
 #include <guichan/font.hpp>
 
@@ -46,7 +46,8 @@ ImageRect TextField::skin;
 TextField::TextField(const std::string &text, bool loseFocusOnTab):
     gcn::TextField(text),
     mNumeric(false),
-    mAutoComplete(false)
+    mAutoComplete(NULL),
+    mHistory(NULL)
 {
     setFrameSize(2);
 
@@ -212,6 +213,42 @@ void TextField::keyPressed(gcn::KeyEvent &keyEvent)
             }
         } break;
 
+        case Key::UP:
+        {
+            if (mHistory && !mHistory->atBegining() && !mHistory->empty())
+            {
+                // Move backward through the history
+                mHistory->current--;
+                setText(*mHistory->current);
+                setCaretPosition(getText().length());
+            }
+        } break;
+
+        case Key::DOWN:
+        {
+            if (mHistory && !mHistory->atEnd())
+            {
+                // Move forward through the history
+                TextHistoryIterator prevHist = mHistory->current++;
+
+                if (!mHistory->atEnd())
+                {
+                    setText(*mHistory->current);
+                    setCaretPosition(getText().length());
+                }
+                else
+                {
+                    setText("");
+                    mHistory->current = prevHist;
+                }
+            }
+            else if (getText() != "")
+            {
+                // Always clear (easy access to useful function)
+                setText("");
+            }
+        } break;
+
         case Key::DELETE:
         {
             unsigned sz = mText.size();
@@ -237,6 +274,19 @@ void TextField::keyPressed(gcn::KeyEvent &keyEvent)
         } break;
 
         case Key::ENTER:
+            if (mHistory)
+            {
+                mHistory->toEnd();
+
+                // If the input is different from previous, put it in the history
+                if (mHistory->empty() || !mHistory->matchesEntry(getText()))
+                {
+                    mHistory->addEntry(getText());
+                }
+
+                mHistory->toEnd();
+            }
+
             distributeActionEvent();
             break;
 
@@ -249,17 +299,7 @@ void TextField::keyPressed(gcn::KeyEvent &keyEvent)
             break;
 
         case Key::TAB:
-            if (mAutoComplete && mText.size() > 0)
-            {
-                std::vector<std::string> names;
-                beingManager->getPlayerNames(names, false);
-                std::string newName = chatWindow->autoComplete(names, mText);
-                if (newName != "")
-                {
-                    setText(newName);
-                    setCaretPosition(mText.size());
-                }
-            }
+            autoComplete();
             if (mLoseFocusOnTab)
                 return;
             break;
@@ -273,6 +313,77 @@ void TextField::keyPressed(gcn::KeyEvent &keyEvent)
     fixScroll();
 }
 
+void TextField::autoComplete()
+{
+    if (mAutoComplete && mText.size() > 0)
+    {
+        int caretPos = getCaretPosition();
+        int startName = 0;
+        const std::string inputText = getText();
+        std::string name = inputText.substr(0, caretPos);
+        std::string newName("");
+
+        for (int f = caretPos - 1; f > -1; f --)
+        {
+            if (isWordSeparator(inputText[f]))
+            {
+                startName = f + 1;
+                name = inputText.substr(f + 1, caretPos - f);
+                break;
+            }
+        }
+
+        if (caretPos - 1 + 1 != startName)
+            return;
+
+
+        std::vector<std::string> nameList;
+        mAutoComplete->getAutoCompleteList(nameList);
+        newName = autocomplete(nameList, name);
+
+        if (newName == "" && mHistory)
+        {
+
+            TextHistoryIterator i = mHistory->history.begin();
+            std::vector<std::string> nameList;
+
+            while (i != mHistory->history.end())
+            {
+                std::string line = *i;
+                unsigned int f = 0;
+                while (f < line.length() && !isWordSeparator(line.at(f)))
+                {
+                    f++;
+                }
+                line = line.substr(0, f);
+                if (line != "")
+                {
+                    nameList.push_back(line);
+                }
+                ++i;
+            }
+
+            newName = autocomplete(nameList, name);
+        }
+
+        if (newName != "")
+        {
+            if(inputText[0] == '@' || inputText[0] == '/')
+                newName = "\"" + newName + "\"";
+
+            setText(inputText.substr(0, startName) + newName
+                    + inputText.substr(caretPos, inputText.length()
+                                       - caretPos));
+
+            if (startName > 0)
+                setCaretPosition(caretPos - name.length() + newName.length()
+                                 + 1);
+            else
+                setCaretPosition(caretPos - name.length() + newName.length());
+        }
+    }
+}
+
 void TextField::handlePaste()
 {
     std::string text = getText();
diff --git a/src/gui/widgets/textfield.h b/src/gui/widgets/textfield.h
index 1e6df9d6..6e50f1e9 100644
--- a/src/gui/widgets/textfield.h
+++ b/src/gui/widgets/textfield.h
@@ -24,9 +24,48 @@
 
 #include <guichan/widgets/textfield.hpp>
 
+#include <vector>
+
 class ImageRect;
 class TextField;
 
+typedef std::list<std::string> TextHistoryList;
+typedef TextHistoryList::iterator TextHistoryIterator;
+
+struct TextHistory {
+    TextHistoryList history;     /**< Command history. */
+    TextHistoryIterator current; /**< History iterator. */
+
+    TextHistory()
+    { current = history.end(); }
+
+    bool empty() const
+    { return history.empty(); }
+
+    bool atBegining() const
+    { return current == history.begin(); }
+
+    bool atEnd() const
+    { return current == history.end(); }
+
+    void toBegining()
+    { current = history.begin(); }
+
+    void toEnd()
+    { current = history.end(); }
+
+    void addEntry(const std::string &text)
+    { history.push_back(text); }
+
+    bool matchesEntry(const std::string &text)
+    { return (*current) == text; }
+};
+
+class AutoCompleteLister {
+public:
+    virtual void getAutoCompleteList(std::vector<std::string>&) const {}
+};
+
 /**
  * A text field.
  *
@@ -91,16 +130,32 @@ class TextField : public gcn::TextField
         int getValue() const;
 
         /**
-         * Set if the tabulator key causes auto complete
+         * Sets the TextField's source of autocomplete. Passing null will
+         * disable autocomplete.
+         */
+         void setAutoComplete(AutoCompleteLister *lister)
+         { mAutoComplete = lister; }
+
+         /**
+         * Returns the TextField's source of autocomplete.
          */
-         void setAutoComplete(bool b ) {mAutoComplete = b;}
+         AutoCompleteLister *getAutoComplete() const
+         { return mAutoComplete; }
 
          /**
-         * Returns if the tabulator key causes auto complete
+         * Sets the TextField's source of input history.
          */
-         bool getAutoComplete() {return mAutoComplete;}
+         void setHistory(TextHistory *history)
+         { mHistory = history; }
+
+         /**
+         * Returns the TextField's source of input history.
+         */
+         TextHistory *getHistory() const
+         { return mHistory; }
 
     private:
+        void autoComplete();
         void handlePaste();
 
         static int instances;
@@ -110,7 +165,10 @@ class TextField : public gcn::TextField
         int mMinimum;
         int mMaximum;
         bool mLoseFocusOnTab;
-        bool mAutoComplete;
+
+        AutoCompleteLister *mAutoComplete;
+
+        TextHistory *mHistory; /**< Text history. */
 };
 
 #endif
diff --git a/src/utils/stringutils.cpp b/src/utils/stringutils.cpp
index 9fe3de14..445427fe 100644
--- a/src/utils/stringutils.cpp
+++ b/src/utils/stringutils.cpp
@@ -174,4 +174,39 @@ const char* getSafeUtf8String(std::string text)
     memcpy(buf, text.c_str(), text.size());
     memset(buf + text.size(), 0, UTF8_MAX_SIZE);
     return buf;
-}
\ No newline at end of file
+}
+
+std::string autocomplete(std::vector<std::string> &candidates,
+                         std::string base)
+{
+    std::vector<std::string>::iterator i = candidates.begin();
+    toLower(base);
+    std::string newName("");
+
+    while (i != candidates.end())
+    {
+        if (!i->empty())
+        {
+            std::string name = *i;
+            toLower(name);
+
+            std::string::size_type pos = name.find(base, 0);
+            if (pos == 0)
+            {
+                if (newName != "")
+                {
+                    toLower(newName);
+                    newName = findSameSubstring(name, newName);
+                }
+                else
+                {
+                    newName = *i;
+                }
+            }
+        }
+
+        ++i;
+    }
+
+    return newName;
+}
diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h
index ec82e240..5f1f05f0 100644
--- a/src/utils/stringutils.h
+++ b/src/utils/stringutils.h
@@ -24,6 +24,7 @@
 
 #include <string>
 #include <sstream>
+#include <vector>
 
 /**
  * Trims spaces off the end and the beginning of the given string.
@@ -125,4 +126,7 @@ const std::string findSameSubstring(const std::string &str1, const std::string &
 
 const char* getSafeUtf8String(std::string text);
 
+std::string autocomplete(std::vector<std::string> &candidates,
+                         std::string base);
+
 #endif // UTILS_STRINGUTILS_H
-- 
cgit v1.2.3-70-g09d2