1 /*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2006-2007 Igor Ulyanov <iulyanov@gmail.com>
4  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 
20 /**
21  * @file
22  * @brief SmartLineEdit class implementation
23  */
24 
25 #include "SmartLineEdit.h"
26 
SmartLineEdit(QWidget * parent,int maxHistorySize)27 SmartLineEdit::SmartLineEdit(QWidget * parent, int maxHistorySize)
28     : HistoryLineEdit(parent, maxHistorySize)
29 {
30     m_whitespace = QRegExp("\\s");
31 
32     m_cmds  = new QStringList();
33     m_nicks = new QStringList();
34     m_sorted_nicks = new QMap<QString, QString>();
35 
36     resetAutoCompletionStatus();
37 
38     // reset autocompletion status when cursor is moved or content is changed
39     connect(this, SIGNAL(cursorPositionChanged(int, int)),
40             this, SLOT(resetAutoCompletionStatus()));
41     connect(this, SIGNAL(textChanged(const QString&)),
42             this, SLOT(resetAutoCompletionStatus()));
43 }
44 
45 
~SmartLineEdit()46 SmartLineEdit::~SmartLineEdit()
47 {
48     delete m_cmds;
49     delete m_nicks;
50     delete m_sorted_nicks;
51 }
52 
53 
addCommands(const QStringList & commands)54 void SmartLineEdit::addCommands(const QStringList & commands)
55 {
56     m_cmds->append(commands);
57 }
58 
59 
removeCommands(const QStringList & commands)60 void SmartLineEdit::removeCommands(const QStringList & commands)
61 {
62     foreach (const QString & cmd, commands)
63     {
64         m_cmds->removeAll(cmd);
65     }
66 }
67 
68 
addNickname(const QString & name)69 void SmartLineEdit::addNickname(const QString & name)
70 {
71     m_sorted_nicks->insert(name.toLower(), name);
72     m_nicks->append(name);
73 }
74 
75 
removeNickname(const QString & name)76 void SmartLineEdit::removeNickname(const QString & name)
77 {
78     m_sorted_nicks->remove(name.toLower());
79     m_nicks->removeAll(name);
80 }
81 
82 
reset()83 void SmartLineEdit::reset()
84 {
85     // forget keywords
86     m_cmds->clear();
87     m_sorted_nicks->clear();
88     m_nicks->clear();
89     resetAutoCompletionStatus();
90 
91     // forget history
92     HistoryLineEdit::reset();
93 }
94 
95 
event(QEvent * event)96 bool SmartLineEdit::event(QEvent * event)
97 {
98     // we only want special treatment for key press events
99     if (event->type() == QEvent::KeyPress)
100     {
101         QKeyEvent * keyEvent = static_cast<QKeyEvent*>(event);
102 
103         // TAB key pressed and any useful chars in the matchMe -> let's process those
104         if ((keyEvent->key() == Qt::Key_Tab) && (!text().trimmed().isEmpty()))
105         {
106             keyPressEvent(keyEvent);
107             if (event->isAccepted())
108                 return true;
109         }
110     }
111     return HistoryLineEdit::event(event);
112 }
113 
keyPressEvent(QKeyEvent * event)114 void SmartLineEdit::keyPressEvent(QKeyEvent * event)
115 {
116     int key = event->key(); // retrieve pressed key
117 
118     // auto-complete on pressed TAB (except for whitespace-only contents)
119     if ((key == Qt::Key_Tab) && (!text().trimmed().isEmpty()))
120     {
121         autoComplete();
122         event->accept();
123     }
124     // clear contents on pressed ESC
125     else if ((event->modifiers() == Qt::NoModifier) && (key == Qt::Key_Escape))
126     {
127         clear();
128         event->accept();
129     }
130     // otherwise forward keys to parent
131     else
132         HistoryLineEdit::keyPressEvent(event);
133 }
134 
135 
autoComplete()136 void SmartLineEdit::autoComplete()
137 {
138     QString match = "";
139     bool isNick = false;
140     QString matchMe = text();
141     QString prefix = "";
142     QString postfix = "";
143     bool isFirstWord;
144 
145     // we are trying to rematch, so use the data from earlier
146     if (m_hasJustMatched)
147     {
148         // restore values from earlier auto-completion
149         matchMe = m_beforeMatch;
150         prefix = m_prefix;
151         postfix = m_postfix;
152         isFirstWord = prefix.isEmpty();
153     }
154     else
155     {
156         m_cmds->sort();
157         m_nicks = new QStringList(m_sorted_nicks->values());
158 
159         int cp = cursorPosition();
160 
161         // cursor is not in or at end/beginning of word
162         if ((cp = matchMe.length()) || (QString(matchMe.at(cp)).contains(m_whitespace)))
163             if ((cp < 1) || (QString(matchMe.at(cp-1)).contains(m_whitespace)))
164                 return;
165 
166         // crop matchMe at cursor position
167         prefix  = matchMe.left (cp);
168         postfix = matchMe.right(matchMe.length()-cp);
169 
170         matchMe = "";
171 
172 
173         // use the whole word the curser is on for matching
174         int prefixLen = prefix.lastIndexOf(m_whitespace) + 1;
175         int preWordLen = prefix.length() - prefixLen;
176         int postWordLen = postfix.indexOf(m_whitespace);
177         int postfixLen = 0;
178         if (postWordLen < 0)
179             postWordLen = postfix.length();
180         else
181             postfixLen = postfix.length() - (postWordLen + 1);
182 
183         matchMe = prefix.right(preWordLen) + postfix.left(postWordLen);
184         prefix  = prefix.left(prefixLen);
185         postfix = postfix.right(postfixLen);
186 
187 
188         isFirstWord = prefix.isEmpty(); // true if first word
189     }
190 
191 
192     if (isFirstWord)
193     {
194         // find matching commands
195         foreach (const QString & cmd, *m_cmds)
196         {
197             if (cmd.startsWith(matchMe, Qt::CaseInsensitive))
198             {
199                 match = cmd;
200 
201                 // move match to end so next time new matches will be preferred
202                 m_cmds->removeAll(cmd);
203                 m_cmds->append(cmd);
204 
205                 break;
206             }
207         }
208     }
209 
210     if (match.isEmpty())
211     {
212         // find matching nicks
213         foreach (const QString & nick, *m_nicks)
214         {
215             if (nick.startsWith(matchMe, Qt::CaseInsensitive))
216             {
217                 match = nick;
218                 isNick = true;
219 
220                 // move match to end so next time new matches will be prefered
221                 m_nicks->removeAll(nick);
222                 m_nicks->append(nick);
223 
224                 break;
225             }
226         }
227     }
228 
229     // we found a single match?
230     if (!match.isEmpty())
231     {
232         // replace last word with match
233         // and append ':' if a name at the beginning of the matchMe got completed
234         // also add a space at the very end to ease further typing
235         QString addAfter = ((isNick && isFirstWord)?": ":" ");
236         match = prefix + match + addAfter;
237         setText(match + postfix);
238 
239         setCursorPosition(match.length());
240 
241         // save values for for the case a rematch is requested
242         m_beforeMatch = matchMe;
243         m_hasJustMatched = true;
244         m_prefix = prefix;
245         m_postfix = postfix;
246     }
247 }
248 
resetAutoCompletionStatus()249 void SmartLineEdit::resetAutoCompletionStatus()
250 {
251     m_beforeMatch = "";
252     m_hasJustMatched = false;
253     m_prefix = "";
254     m_postfix = "";
255 }
256 
257