1 /****************************************************************************
2 **
3 ** Copyright (C) 2006-2009 fullmetalcoder <fullmetalcoder@hotmail.fr>
4 **
5 ** This file is part of the Edyuk project <http://edyuk.org>
6 **
7 ** This file may be used under the terms of the GNU General Public License
8 ** version 3 as published by the Free Software Foundation and appearing in the
9 ** file GPL.txt included in the packaging of this file.
10 **
11 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
12 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
13 **
14 ****************************************************************************/
15
16 #include "qcodecompletionengine.h"
17
18 /*!
19 \file qcompletionengine.cpp
20 \brief Implementation of the QCodeCompletionEngine class.
21 */
22
23 #include "qeditor.h"
24
25 #include <QAction>
26 #include <QKeyEvent>
27 #include <QTextCursor>
28
29 #ifdef _QCODE_MODEL_
30 #include "qcodebuffer.h"
31 #endif
32
33 /*!
34
35 */
QCodeCompletionEngine(QObject * p)36 QCodeCompletionEngine::QCodeCompletionEngine(QObject *p)
37 : QObject(p), m_max(0)
38 {
39 pForcedTrigger = new QAction(tr("&Trigger completion"), this);
40
41 connect(pForcedTrigger , SIGNAL( triggered() ),
42 this , SLOT ( complete() ) );
43
44 }
45
46 /*!
47
48 */
~QCodeCompletionEngine()49 QCodeCompletionEngine::~QCodeCompletionEngine()
50 {
51
52 }
53
54 /*!
55 \return
56 */
triggerAction() const57 QAction* QCodeCompletionEngine::triggerAction() const
58 {
59 return pForcedTrigger;
60 }
61
62 /*!
63
64 */
retranslate()65 void QCodeCompletionEngine::retranslate()
66 {
67 pForcedTrigger->setText(tr("&Trigger completion"));
68 }
69
70 /*!
71
72 */
triggers() const73 QStringList QCodeCompletionEngine::triggers() const
74 {
75 return m_triggers;
76 }
77
78 /*!
79
80 */
addTrigger(const QString & s)81 void QCodeCompletionEngine::addTrigger(const QString& s)
82 {
83 if ( m_triggers.contains(s) )
84 return;
85
86 if ( s.count() > m_max )
87 m_max = s.count();
88
89 m_triggers << s;
90 }
91
92 /*!
93
94 */
removeTrigger(const QString & s)95 void QCodeCompletionEngine::removeTrigger(const QString& s)
96 {
97 m_triggers.removeAll(s);
98 }
99
100 /*!
101
102 */
setCodeModel(QCodeModel * m)103 void QCodeCompletionEngine::setCodeModel(QCodeModel *m)
104 {
105 Q_UNUSED(m)
106 }
107
108 /*!
109
110 */
editor() const111 QEditor* QCodeCompletionEngine::editor() const
112 {
113 return pEdit;
114 }
115
116 /*!
117 \brief Attach the completion engine instance to a new editor object
118 */
setEditor(QEditor * e)119 void QCodeCompletionEngine::setEditor(QEditor *e)
120 {
121 if ( pEdit )
122 {
123 pEdit->removeAction(pForcedTrigger, "&Edit");
124 //pEdit->removeEventFilter(this);
125
126 disconnect( pEdit , SIGNAL( textEdited(QKeyEvent*) ),
127 this , SLOT ( textEdited(QKeyEvent*) ) );
128 }
129
130 pEdit = e;
131
132 if ( pEdit )
133 {
134 //pEdit->installEventFilter(this);
135 pEdit->addAction(pForcedTrigger, "&Edit");
136
137 connect(pEdit , SIGNAL( textEdited(QKeyEvent*) ),
138 this , SLOT ( textEdited(QKeyEvent*) ) );
139 }
140 }
141
142 /*!
143 \internal
144 */
run()145 void QCodeCompletionEngine::run()
146 {
147 if ( m_cur.isNull() )
148 return;
149
150 //qDebug("complete!");
151
152 complete(m_cur, m_trig);
153
154 m_cur = QDocumentCursor();
155 m_trig.clear();
156 }
157
158 /*!
159 \brief Forced completion trigger
160 */
complete()161 void QCodeCompletionEngine::complete()
162 {
163 complete(editor()->cursor(), QString());
164 }
165
166 /*!
167 \brief Standard completion entry point for QEditor
168 \param e QKeyEvent that caused a modification of the text
169
170 \note This slot is only called when editing happens without
171 any cursor mirrors
172 */
textEdited(QKeyEvent * k)173 void QCodeCompletionEngine::textEdited(QKeyEvent *k)
174 {
175 QString s, txt = s = k->text();
176 QDocumentCursor cur = editor()->cursor();
177
178 int count = txt.count();
179
180 if ( txt.isEmpty() || m_triggers.isEmpty() )
181 return;
182
183 //qDebug("should trigger completion? (bis)");
184
185 if ( count > m_max )
186 {
187 txt = txt.right(m_max);
188
189 } else if ( count < m_max ) {
190
191 QDocumentCursor c(cur);
192 c.movePosition(m_max, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
193
194 //qDebug("prev text : %s", qPrintable(c.selectedText()));
195
196 txt = c.selectedText();
197 }
198
199 //qDebug("text : %s", qPrintable(txt));
200
201 foreach ( QString trig, m_triggers )
202 {
203 if ( txt.endsWith(trig) )
204 {
205 cur = editor()->cursor();
206 cur.movePosition(trig.count(), QDocumentCursor::PreviousCharacter);
207
208 // notify completion trigger
209 emit completionTriggered(trig);
210
211 //get rid of previous calltips/completions
212 editor()->setFocus();
213
214 // trigger completion
215 complete(cur, trig);
216 }
217 }
218 }
219
220 /*!
221 \internal
222 */
eventFilter(QObject * o,QEvent * e)223 bool QCodeCompletionEngine::eventFilter(QObject *o, QEvent *e)
224 {
225 if ( !e || !o || (e->type() != QEvent::KeyPress) || (o != pEdit) )
226 return false;
227
228 //qDebug("should trigger completion?");
229
230 QDocumentCursor cur = editor()->cursor();
231 QKeyEvent *k = static_cast<QKeyEvent*>(e);
232
233 QString s, txt = s = k->text();
234
235 int count = txt.count();
236
237 if ( txt.isEmpty() || m_triggers.isEmpty() )
238 return false; // QThread::eventFilter(o, e);
239
240 //qDebug("should trigger completion? (bis)");
241
242 if ( count > m_max )
243 {
244 txt = txt.right(m_max);
245
246 } else if ( count < m_max ) {
247
248 QDocumentCursor c(cur);
249 c.movePosition(m_max - count, QDocumentCursor::Left, QDocumentCursor::KeepAnchor);
250
251 //qDebug("prev text : %s", qPrintable(c.selectedText()));
252
253 txt.prepend(c.selectedText());
254 }
255
256 //qDebug("text : %s", qPrintable(txt));
257
258 foreach ( QString trig, m_triggers )
259 {
260 if ( txt.endsWith(trig) )
261 {
262 editor()->write(s);
263
264 cur = editor()->cursor();
265 cur.movePosition(trig.count(), QDocumentCursor::PreviousCharacter);
266
267 // notify completion trigger
268 emit completionTriggered(trig);
269
270 //get rid of previous calltips/completions
271 editor()->setFocus();
272
273 // trigger completion
274 complete(cur, trig);
275
276 return true;
277 }
278 }
279
280 return false;
281 }
282
283 /*!
284 \brief Completion callback
285 */
complete(const QDocumentCursor & c,const QString & trigger)286 void QCodeCompletionEngine::complete(const QDocumentCursor& c, const QString& trigger)
287 {
288 #ifdef _QCODE_MODEL_
289 // TODO :
290 // * use a more efficient design by avoiding deep copy of the data
291 // * only lex the requested part (stop at cursor or topmost frame required for proper class hierarchy)
292
293 QDocumentCursor cc = c;
294 cc.movePosition(1, QDocumentCursor::Start, QDocumentCursor::KeepAnchor);
295
296 //qDebug("%s", qPrintable(cc.selectedText()));
297
298 QCodeBuffer buffer(cc.selectedText());
299 //QCodeBuffer buffer(c.document()->text());
300 complete(&buffer, trigger);
301 #else
302 Q_UNUSED(c)
303 Q_UNUSED(trigger)
304 qWarning("From complete(QDocumentCursor, QString)");
305 qWarning("QCodeCompletionEngine is not self-sufficient : subclasses should "
306 "reimplement at least on of the complete() method...");
307 #endif
308 }
309
310 /*!
311 \overload
312 \brief Overloaded completion callback
313 */
complete(QCodeStream * s,const QString & trigger)314 void QCodeCompletionEngine::complete(QCodeStream *s, const QString& trigger)
315 {
316 Q_UNUSED(s)
317 Q_UNUSED(trigger)
318
319 qWarning("From complete(QCodeStream*, QString)");
320 qWarning("QCodeCompletionEngine is not self-sufficient : subclasses should"
321 "reimplement at least on of the complete() method...");
322 }
323