QTfrontend/ui/widget/SmartLineEdit.cpp
author nemo
Fri, 23 Mar 2012 18:20:59 -0400
changeset 6810 5337f554480e
parent 6700 e04da46ee43c
child 6952 7f70f37bbf08
permissions -rw-r--r--
This has bugged me for a while. Since we are missing the source SVGs for this theme, removed the leaves crudely in GIMP. Also added some basic roots. Someone more artistic is encouraged to try and improve it.

/*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2006-2007 Igor Ulyanov <iulyanov@gmail.com>
 * Copyright (c) 2007-2012 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program 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; version 2 of the License
 *
 * This program 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

/**
 * @file
 * @brief SmartLineEdit class implementation
 */

#include "SmartLineEdit.h"

SmartLineEdit::SmartLineEdit(QWidget * parent, int maxHistorySize)
    : HistoryLineEdit(parent, maxHistorySize)
{
    m_whitespace = QRegExp("\\s");

    m_cmds  = new QStringList();
    m_nicks = new QStringList();
    m_sorted_nicks = new QMap<QString, QString>();

    resetAutoCompletionStatus();

    // reset autocompletion status when cursor is moved or content is changed
    connect(this, SIGNAL(cursorPositionChanged(int, int)),
            this, SLOT(resetAutoCompletionStatus()));
    connect(this, SIGNAL(textChanged(const QString&)),
            this, SLOT(resetAutoCompletionStatus()));
}


SmartLineEdit::~SmartLineEdit()
{
    delete m_cmds;
    delete m_nicks;
    delete m_sorted_nicks;
}


void SmartLineEdit::addCommands(const QStringList & commands)
{
    m_cmds->append(commands);
}


void SmartLineEdit::removeCommands(const QStringList & commands)
{
    foreach (const QString & cmd, commands)
    {
        m_cmds->removeAll(cmd);
    }
}


void SmartLineEdit::addNickname(const QString & name)
{
    m_sorted_nicks->insert(name.toLower(), name);
    m_nicks->append(name);
}


void SmartLineEdit::removeNickname(const QString & name)
{
    m_sorted_nicks->remove(name.toLower());
    m_nicks->removeAll(name);
}


void SmartLineEdit::reset()
{
    // forget keywords
    m_cmds->clear();
    m_sorted_nicks->clear();
    m_nicks->clear();
    resetAutoCompletionStatus();

    // forget history
    HistoryLineEdit::reset();
}


bool SmartLineEdit::event(QEvent * event)
{
    // we only want special treatment for key press events
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent * keyEvent = static_cast<QKeyEvent*>(event);

        // TAB key pressed and any useful chars in the matchMe -> let's process those
        if ((keyEvent->key() == Qt::Key_Tab) && (!text().trimmed().isEmpty()))
        {
            keyPressEvent(keyEvent);
            if (event->isAccepted())
                return true;
        }
    }
    return HistoryLineEdit::event(event);
}

void SmartLineEdit::keyPressEvent(QKeyEvent * event)
{
    int key = event->key(); // retrieve pressed key

    // auto-complete on pressed TAB (except for whitespace-only contents)
    if ((key == Qt::Key_Tab) && (!text().trimmed().isEmpty()))
    {
        autoComplete();
        event->accept();
    }
    // clear contents on pressed ESC
    else if ((event->modifiers() == Qt::NoModifier) && (key == Qt::Key_Escape))
    {
        clear();
        event->accept();
    }
    // otherwise forward keys to parent
    else
        HistoryLineEdit::keyPressEvent(event);
}


void SmartLineEdit::autoComplete()
{
    QString match = "";
    bool isNick = false;
    QString matchMe = text();
    QString prefix = "";
    QString postfix = "";
    bool isFirstWord;

    // we are trying to rematch, so use the data from earlier
    if (m_hasJustMatched)
    {
        // restore values from earlier auto-completion
        matchMe = m_beforeMatch;
        prefix = m_prefix;
        postfix = m_postfix;
        isFirstWord = prefix.isEmpty();
    }
    else
    {
        m_cmds->sort();
        m_nicks = new QStringList(m_sorted_nicks->values());

        int cp = cursorPosition();

        // cursor is not in or at end/beginning of word
        if ((cp = matchMe.length()) || (QString(matchMe.at(cp)).contains(m_whitespace)))
            if ((cp < 1) || (QString(matchMe.at(cp-1)).contains(m_whitespace)))
                return;

        // crop matchMe at cursor position
        prefix  = matchMe.left (cp);
        postfix = matchMe.right(matchMe.length()-cp);

        matchMe = "";


        // use the whole word the curser is on for matching
        int prefixLen = prefix.lastIndexOf(m_whitespace) + 1;
        int preWordLen = prefix.length() - prefixLen;
        int postWordLen = postfix.indexOf(m_whitespace);
        int postfixLen = 0;
        if (postWordLen < 0)
            postWordLen = postfix.length();
        else
            postfixLen = postfix.length() - (postWordLen + 1);

        matchMe = prefix.right(preWordLen) + postfix.left(postWordLen);
        prefix  = prefix.left(prefixLen);
        postfix = postfix.right(postfixLen);


        isFirstWord = prefix.isEmpty(); // true if first word
    }


    if (isFirstWord)
    {
        // find matching commands
        foreach (const QString & cmd, *m_cmds)
        {
            if (cmd.startsWith(matchMe, Qt::CaseInsensitive))
            {
                match = cmd;

                // move match to end so next time new matches will be preferred
                m_cmds->removeAll(cmd);
                m_cmds->append(cmd);

                break;
            }
        }
    }

    if (match.isEmpty())
    {
        // find matching nicks
        foreach (const QString & nick, *m_nicks)
        {
            if (nick.startsWith(matchMe, Qt::CaseInsensitive))
            {
                match = nick;
                isNick = true;

                // move match to end so next time new matches will be prefered
                m_nicks->removeAll(nick);
                m_nicks->append(nick);

                break;
            }
        }
    }

    // we found a single match?
    if (!match.isEmpty())
    {
        // replace last word with match
        // and append ':' if a name at the beginning of the matchMe got completed
        // also add a space at the very end to ease further typing
        QString addAfter = ((isNick && isFirstWord)?": ":" ");
        match = prefix + match + addAfter;
        setText(match + postfix);

        setCursorPosition(match.length());

        // save values for for the case a rematch is requested
        m_beforeMatch = matchMe;
        m_hasJustMatched = true;
        m_prefix = prefix;
        m_postfix = postfix;
    }
}

void SmartLineEdit::resetAutoCompletionStatus()
{
    m_beforeMatch = "";
    m_hasJustMatched = false;
    m_prefix = "";
    m_postfix = "";
}