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