1 | /****************************************************************************
|
---|
2 | **
|
---|
3 | ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
---|
4 | ** Contact: Qt Software Information ([email protected])
|
---|
5 | **
|
---|
6 | ** This file is part of the QtSCriptTools module of the Qt Toolkit.
|
---|
7 | **
|
---|
8 | ** $QT_BEGIN_LICENSE:LGPL$
|
---|
9 | ** Commercial Usage
|
---|
10 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
---|
11 | ** accordance with the Qt Commercial License Agreement provided with the
|
---|
12 | ** Software or, alternatively, in accordance with the terms contained in
|
---|
13 | ** a written agreement between you and Nokia.
|
---|
14 | **
|
---|
15 | ** GNU Lesser General Public License Usage
|
---|
16 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
---|
17 | ** General Public License version 2.1 as published by the Free Software
|
---|
18 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
---|
19 | ** packaging of this file. Please review the following information to
|
---|
20 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
---|
21 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
---|
22 | **
|
---|
23 | ** In addition, as a special exception, Nokia gives you certain
|
---|
24 | ** additional rights. These rights are described in the Nokia Qt LGPL
|
---|
25 | ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
|
---|
26 | ** package.
|
---|
27 | **
|
---|
28 | ** GNU General Public License Usage
|
---|
29 | ** Alternatively, this file may be used under the terms of the GNU
|
---|
30 | ** General Public License version 3.0 as published by the Free Software
|
---|
31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
---|
32 | ** packaging of this file. Please review the following information to
|
---|
33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
---|
34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
---|
35 | **
|
---|
36 | ** If you are unsure which license is appropriate for your use, please
|
---|
37 | ** contact the sales department at [email protected].
|
---|
38 | ** $QT_END_LICENSE$
|
---|
39 | **
|
---|
40 | ****************************************************************************/
|
---|
41 |
|
---|
42 | #include "qscriptdebuggerconsolewidget_p.h"
|
---|
43 | #include "qscriptdebuggerconsolewidgetinterface_p_p.h"
|
---|
44 | #include "qscriptdebuggerconsolehistorianinterface_p.h"
|
---|
45 | #include "qscriptcompletionproviderinterface_p.h"
|
---|
46 | #include "qscriptcompletiontaskinterface_p.h"
|
---|
47 |
|
---|
48 | #include <QtCore/qdebug.h>
|
---|
49 | #include <QtGui/qplaintextedit.h>
|
---|
50 | #include <QtGui/qlabel.h>
|
---|
51 | #include <QtGui/qlineedit.h>
|
---|
52 | #include <QtGui/qlistview.h>
|
---|
53 | #include <QtGui/qscrollbar.h>
|
---|
54 | #include <QtGui/qboxlayout.h>
|
---|
55 | #include <QtGui/qcompleter.h>
|
---|
56 |
|
---|
57 | QT_BEGIN_NAMESPACE
|
---|
58 |
|
---|
59 | namespace {
|
---|
60 |
|
---|
61 | class PromptLabel : public QLabel
|
---|
62 | {
|
---|
63 | public:
|
---|
64 | PromptLabel(QWidget *parent = 0)
|
---|
65 | : QLabel(parent)
|
---|
66 | {
|
---|
67 | setFrameShape(QFrame::NoFrame);
|
---|
68 | setIndent(2);
|
---|
69 | setMargin(2);
|
---|
70 | setSizePolicy(QSizePolicy::Minimum, sizePolicy().verticalPolicy());
|
---|
71 | setAlignment(Qt::AlignHCenter);
|
---|
72 | #ifndef QT_NO_STYLE_STYLESHEET
|
---|
73 | setStyleSheet(QLatin1String("background: white;"));
|
---|
74 | #endif
|
---|
75 | }
|
---|
76 |
|
---|
77 | QSize sizeHint() const {
|
---|
78 | QFontMetrics fm(font());
|
---|
79 | return fm.size(0, text()) + QSize(8, 0);
|
---|
80 | }
|
---|
81 | };
|
---|
82 |
|
---|
83 | class InputEdit : public QLineEdit
|
---|
84 | {
|
---|
85 | public:
|
---|
86 | InputEdit(QWidget *parent = 0)
|
---|
87 | : QLineEdit(parent)
|
---|
88 | {
|
---|
89 | setFrame(false);
|
---|
90 | setSizePolicy(QSizePolicy::MinimumExpanding, sizePolicy().verticalPolicy());
|
---|
91 | }
|
---|
92 | };
|
---|
93 |
|
---|
94 | class CommandLine : public QWidget
|
---|
95 | {
|
---|
96 | Q_OBJECT
|
---|
97 | public:
|
---|
98 | CommandLine(QWidget *parent = 0)
|
---|
99 | : QWidget(parent)
|
---|
100 | {
|
---|
101 | promptLabel = new PromptLabel();
|
---|
102 | inputEdit = new InputEdit();
|
---|
103 | QHBoxLayout *hbox = new QHBoxLayout(this);
|
---|
104 | hbox->setSpacing(0);
|
---|
105 | hbox->setMargin(0);
|
---|
106 | hbox->addWidget(promptLabel);
|
---|
107 | hbox->addWidget(inputEdit);
|
---|
108 |
|
---|
109 | QObject::connect(inputEdit, SIGNAL(returnPressed()),
|
---|
110 | this, SLOT(onReturnPressed()));
|
---|
111 | QObject::connect(inputEdit, SIGNAL(textEdited(QString)),
|
---|
112 | this, SIGNAL(lineEdited(QString)));
|
---|
113 |
|
---|
114 | setFocusProxy(inputEdit);
|
---|
115 | }
|
---|
116 |
|
---|
117 | QString prompt() const
|
---|
118 | {
|
---|
119 | return promptLabel->text();
|
---|
120 | }
|
---|
121 | void setPrompt(const QString &prompt)
|
---|
122 | {
|
---|
123 | promptLabel->setText(prompt);
|
---|
124 | }
|
---|
125 |
|
---|
126 | QString input() const
|
---|
127 | {
|
---|
128 | return inputEdit->text();
|
---|
129 | }
|
---|
130 | void setInput(const QString &input)
|
---|
131 | {
|
---|
132 | inputEdit->setText(input);
|
---|
133 | }
|
---|
134 |
|
---|
135 | int cursorPosition() const
|
---|
136 | {
|
---|
137 | return inputEdit->cursorPosition();
|
---|
138 | }
|
---|
139 | void setCursorPosition(int position)
|
---|
140 | {
|
---|
141 | inputEdit->setCursorPosition(position);
|
---|
142 | }
|
---|
143 |
|
---|
144 | QWidget *editor() const
|
---|
145 | {
|
---|
146 | return inputEdit;
|
---|
147 | }
|
---|
148 |
|
---|
149 | Q_SIGNALS:
|
---|
150 | void lineEntered(const QString &contents);
|
---|
151 | void lineEdited(const QString &contents);
|
---|
152 |
|
---|
153 | private Q_SLOTS:
|
---|
154 | void onReturnPressed()
|
---|
155 | {
|
---|
156 | QString text = inputEdit->text();
|
---|
157 | inputEdit->clear();
|
---|
158 | emit lineEntered(text);
|
---|
159 | }
|
---|
160 |
|
---|
161 | private:
|
---|
162 | PromptLabel *promptLabel;
|
---|
163 | InputEdit *inputEdit;
|
---|
164 | };
|
---|
165 |
|
---|
166 | class OutputEdit : public QPlainTextEdit
|
---|
167 | {
|
---|
168 | public:
|
---|
169 | OutputEdit(QWidget *parent = 0)
|
---|
170 | : QPlainTextEdit(parent)
|
---|
171 | {
|
---|
172 | setFrameShape(QFrame::NoFrame);
|
---|
173 | setReadOnly(true);
|
---|
174 | // ### there's no context menu when the edit can't have focus,
|
---|
175 | // even though you can select text in it.
|
---|
176 | // setFocusPolicy(Qt::NoFocus);
|
---|
177 | setMaximumBlockCount(2500);
|
---|
178 | }
|
---|
179 |
|
---|
180 | void scrollToBottom()
|
---|
181 | {
|
---|
182 | QScrollBar *bar = verticalScrollBar();
|
---|
183 | bar->setValue(bar->maximum());
|
---|
184 | }
|
---|
185 |
|
---|
186 | int charactersPerLine() const
|
---|
187 | {
|
---|
188 | QFontMetrics fm(font());
|
---|
189 | return width() / fm.maxWidth();
|
---|
190 | }
|
---|
191 | };
|
---|
192 |
|
---|
193 | } // namespace
|
---|
194 |
|
---|
195 | class QScriptDebuggerConsoleWidgetPrivate
|
---|
196 | : public QScriptDebuggerConsoleWidgetInterfacePrivate
|
---|
197 | {
|
---|
198 | Q_DECLARE_PUBLIC(QScriptDebuggerConsoleWidget)
|
---|
199 | public:
|
---|
200 | QScriptDebuggerConsoleWidgetPrivate();
|
---|
201 | ~QScriptDebuggerConsoleWidgetPrivate();
|
---|
202 |
|
---|
203 | // private slots
|
---|
204 | void _q_onLineEntered(const QString &contents);
|
---|
205 | void _q_onLineEdited(const QString &contents);
|
---|
206 | void _q_onCompletionTaskFinished();
|
---|
207 |
|
---|
208 | CommandLine *commandLine;
|
---|
209 | OutputEdit *outputEdit;
|
---|
210 | int historyIndex;
|
---|
211 | QString newInput;
|
---|
212 | };
|
---|
213 |
|
---|
214 | QScriptDebuggerConsoleWidgetPrivate::QScriptDebuggerConsoleWidgetPrivate()
|
---|
215 | {
|
---|
216 | historyIndex = -1;
|
---|
217 | }
|
---|
218 |
|
---|
219 | QScriptDebuggerConsoleWidgetPrivate::~QScriptDebuggerConsoleWidgetPrivate()
|
---|
220 | {
|
---|
221 | }
|
---|
222 |
|
---|
223 | void QScriptDebuggerConsoleWidgetPrivate::_q_onLineEntered(const QString &contents)
|
---|
224 | {
|
---|
225 | Q_Q(QScriptDebuggerConsoleWidget);
|
---|
226 | outputEdit->appendPlainText(QString::fromLatin1("%0 %1").arg(commandLine->prompt()).arg(contents));
|
---|
227 | outputEdit->scrollToBottom();
|
---|
228 | historyIndex = -1;
|
---|
229 | newInput.clear();
|
---|
230 | emit q->lineEntered(contents);
|
---|
231 | }
|
---|
232 |
|
---|
233 | void QScriptDebuggerConsoleWidgetPrivate::_q_onLineEdited(const QString &contents)
|
---|
234 | {
|
---|
235 | if (historyIndex != -1) {
|
---|
236 | // ### try to get the bash behavior...
|
---|
237 | #if 0
|
---|
238 | historian->changeHistoryAt(historyIndex, contents);
|
---|
239 | #endif
|
---|
240 | } else {
|
---|
241 | newInput = contents;
|
---|
242 | }
|
---|
243 | }
|
---|
244 |
|
---|
245 | static bool lengthLessThan(const QString &s1, const QString &s2)
|
---|
246 | {
|
---|
247 | return s1.length() < s2.length();
|
---|
248 | }
|
---|
249 |
|
---|
250 | // input must be sorted by length already
|
---|
251 | static QString longestCommonPrefix(const QStringList &lst)
|
---|
252 | {
|
---|
253 | QString result = lst.last();
|
---|
254 | for (int i = lst.size() - 2; (i >= 0) && !result.isEmpty(); --i) {
|
---|
255 | const QString &s = lst.at(i);
|
---|
256 | int j = 0;
|
---|
257 | for ( ; (j < qMin(s.length(), result.length())) && (s.at(j) == result.at(j)); ++j)
|
---|
258 | ;
|
---|
259 | result = result.left(j);
|
---|
260 | }
|
---|
261 | return result;
|
---|
262 | }
|
---|
263 |
|
---|
264 | void QScriptDebuggerConsoleWidgetPrivate::_q_onCompletionTaskFinished()
|
---|
265 | {
|
---|
266 | QScriptCompletionTaskInterface *task = 0;
|
---|
267 | task = qobject_cast<QScriptCompletionTaskInterface*>(q_func()->sender());
|
---|
268 | if (task->resultCount() == 1) {
|
---|
269 | QString completion = task->resultAt(0);
|
---|
270 | completion.append(task->appendix());
|
---|
271 | QString tmp = commandLine->input();
|
---|
272 | tmp.remove(task->position(), task->length());
|
---|
273 | tmp.insert(task->position(), completion);
|
---|
274 | commandLine->setInput(tmp);
|
---|
275 | } else if (task->resultCount() > 1) {
|
---|
276 | {
|
---|
277 | QStringList lst;
|
---|
278 | for (int i = 0; i < task->resultCount(); ++i)
|
---|
279 | lst.append(task->resultAt(i).mid(task->length()));
|
---|
280 | qSort(lst.begin(), lst.end(), lengthLessThan);
|
---|
281 | QString lcp = longestCommonPrefix(lst);
|
---|
282 | if (!lcp.isEmpty()) {
|
---|
283 | QString tmp = commandLine->input();
|
---|
284 | tmp.insert(task->position() + task->length(), lcp);
|
---|
285 | commandLine->setInput(tmp);
|
---|
286 | }
|
---|
287 | }
|
---|
288 |
|
---|
289 | outputEdit->appendPlainText(QString::fromLatin1("%0 %1")
|
---|
290 | .arg(commandLine->prompt()).arg(commandLine->input()));
|
---|
291 | int maxLength = 0;
|
---|
292 | for (int i = 0; i < task->resultCount(); ++i)
|
---|
293 | maxLength = qMax(maxLength, task->resultAt(i).length());
|
---|
294 | Q_ASSERT(maxLength > 0);
|
---|
295 | int tab = 8;
|
---|
296 | int columns = qMax(1, outputEdit->charactersPerLine() / (maxLength + tab));
|
---|
297 | QString msg;
|
---|
298 | for (int i = 0; i < task->resultCount(); ++i) {
|
---|
299 | if (i != 0) {
|
---|
300 | if ((i % columns) == 0) {
|
---|
301 | outputEdit->appendPlainText(msg);
|
---|
302 | msg.clear();
|
---|
303 | } else {
|
---|
304 | int pad = maxLength + tab - (msg.length() % (maxLength + tab));
|
---|
305 | msg.append(QString(pad, QLatin1Char(' ')));
|
---|
306 | }
|
---|
307 | }
|
---|
308 | msg.append(task->resultAt(i));
|
---|
309 | }
|
---|
310 | if (!msg.isEmpty())
|
---|
311 | outputEdit->appendPlainText(msg);
|
---|
312 | outputEdit->scrollToBottom();
|
---|
313 | }
|
---|
314 | task->deleteLater();
|
---|
315 | }
|
---|
316 |
|
---|
317 | QScriptDebuggerConsoleWidget::QScriptDebuggerConsoleWidget(QWidget *parent)
|
---|
318 | : QScriptDebuggerConsoleWidgetInterface(*new QScriptDebuggerConsoleWidgetPrivate, parent, 0)
|
---|
319 | {
|
---|
320 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
321 | d->commandLine = new CommandLine();
|
---|
322 | d->commandLine->setPrompt(QString::fromLatin1("qsdb>"));
|
---|
323 | d->outputEdit = new OutputEdit();
|
---|
324 | QVBoxLayout *vbox = new QVBoxLayout(this);
|
---|
325 | vbox->setSpacing(0);
|
---|
326 | vbox->setMargin(0);
|
---|
327 | vbox->addWidget(d->outputEdit);
|
---|
328 | vbox->addWidget(d->commandLine);
|
---|
329 |
|
---|
330 | #if 0
|
---|
331 | QString sheet = QString::fromLatin1("background-color: black;"
|
---|
332 | "color: aquamarine;"
|
---|
333 | "font-size: 14px;"
|
---|
334 | "font-family: \"Monospace\"");
|
---|
335 | #endif
|
---|
336 | #ifndef QT_NO_STYLE_STYLESHEET
|
---|
337 | QString sheet = QString::fromLatin1("font-size: 14px; font-family: \"Monospace\";");
|
---|
338 | setStyleSheet(sheet);
|
---|
339 | #endif
|
---|
340 |
|
---|
341 | QObject::connect(d->commandLine, SIGNAL(lineEntered(QString)),
|
---|
342 | this, SLOT(_q_onLineEntered(QString)));
|
---|
343 | QObject::connect(d->commandLine, SIGNAL(lineEdited(QString)),
|
---|
344 | this, SLOT(_q_onLineEdited(QString)));
|
---|
345 | }
|
---|
346 |
|
---|
347 | QScriptDebuggerConsoleWidget::~QScriptDebuggerConsoleWidget()
|
---|
348 | {
|
---|
349 | }
|
---|
350 |
|
---|
351 | void QScriptDebuggerConsoleWidget::message(
|
---|
352 | QtMsgType type, const QString &text, const QString &fileName,
|
---|
353 | int lineNumber, int columnNumber, const QVariant &/*data*/)
|
---|
354 | {
|
---|
355 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
356 | QString msg;
|
---|
357 | if (!fileName.isEmpty() || (lineNumber != -1)) {
|
---|
358 | if (!fileName.isEmpty())
|
---|
359 | msg.append(fileName);
|
---|
360 | else
|
---|
361 | msg.append(QLatin1String("<noname>"));
|
---|
362 | if (lineNumber != -1) {
|
---|
363 | msg.append(QLatin1Char(':'));
|
---|
364 | msg.append(QString::number(lineNumber));
|
---|
365 | if (columnNumber != -1) {
|
---|
366 | msg.append(QLatin1Char(':'));
|
---|
367 | msg.append(QString::number(columnNumber));
|
---|
368 | }
|
---|
369 | }
|
---|
370 | msg.append(QLatin1String(": "));
|
---|
371 | }
|
---|
372 | msg.append(text);
|
---|
373 | QTextCharFormat oldFmt = d->outputEdit->currentCharFormat();
|
---|
374 | QTextCharFormat fmt(oldFmt);
|
---|
375 | if (type == QtCriticalMsg) {
|
---|
376 | fmt.setForeground(Qt::red);
|
---|
377 | d->outputEdit->setCurrentCharFormat(fmt);
|
---|
378 | }
|
---|
379 | d->outputEdit->appendPlainText(msg);
|
---|
380 | d->outputEdit->setCurrentCharFormat(oldFmt);
|
---|
381 | d->outputEdit->scrollToBottom();
|
---|
382 | }
|
---|
383 |
|
---|
384 | void QScriptDebuggerConsoleWidget::setLineContinuationMode(bool enabled)
|
---|
385 | {
|
---|
386 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
387 | QString prompt = enabled
|
---|
388 | ? QString::fromLatin1("....")
|
---|
389 | : QString::fromLatin1("qsdb>");
|
---|
390 | d->commandLine->setPrompt(prompt);
|
---|
391 | }
|
---|
392 |
|
---|
393 | void QScriptDebuggerConsoleWidget::clear()
|
---|
394 | {
|
---|
395 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
396 | d->outputEdit->clear();
|
---|
397 | }
|
---|
398 |
|
---|
399 | void QScriptDebuggerConsoleWidget::keyPressEvent(QKeyEvent *event)
|
---|
400 | {
|
---|
401 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
402 | if (event->key() == Qt::Key_Up) {
|
---|
403 | if (d->historyIndex+1 == d->historian->historyCount())
|
---|
404 | return;
|
---|
405 | QString cmd = d->historian->historyAt(++d->historyIndex);
|
---|
406 | d->commandLine->setInput(cmd);
|
---|
407 | } else if (event->key() == Qt::Key_Down) {
|
---|
408 | if (d->historyIndex == -1) {
|
---|
409 | // nothing to do
|
---|
410 | } else if (d->historyIndex == 0) {
|
---|
411 | d->commandLine->setInput(d->newInput);
|
---|
412 | --d->historyIndex;
|
---|
413 | } else {
|
---|
414 | QString cmd = d->historian->historyAt(--d->historyIndex);
|
---|
415 | d->commandLine->setInput(cmd);
|
---|
416 | }
|
---|
417 | } else if (event->key() == Qt::Key_Tab) {
|
---|
418 | QScriptCompletionTaskInterface *task = 0;
|
---|
419 | task = d->completionProvider->createCompletionTask(
|
---|
420 | d->commandLine->input(), d->commandLine->cursorPosition(),
|
---|
421 | /*frameIndex=*/-1, // current frame
|
---|
422 | QScriptCompletionProviderInterface::ConsoleCommandCompletion);
|
---|
423 | QObject::connect(task, SIGNAL(finished()),
|
---|
424 | this, SLOT(_q_onCompletionTaskFinished()));
|
---|
425 | task->start();
|
---|
426 | } else {
|
---|
427 | QScriptDebuggerConsoleWidgetInterface::keyPressEvent(event);
|
---|
428 | }
|
---|
429 | }
|
---|
430 |
|
---|
431 | bool QScriptDebuggerConsoleWidget::focusNextPrevChild(bool b)
|
---|
432 | {
|
---|
433 | Q_D(QScriptDebuggerConsoleWidget);
|
---|
434 | if (d->outputEdit->hasFocus())
|
---|
435 | return QScriptDebuggerConsoleWidgetInterface::focusNextPrevChild(b);
|
---|
436 | else
|
---|
437 | return false;
|
---|
438 | }
|
---|
439 |
|
---|
440 | QT_END_NAMESPACE
|
---|
441 |
|
---|
442 | #include "qscriptdebuggerconsolewidget.moc"
|
---|
443 |
|
---|
444 | #include "moc_qscriptdebuggerconsolewidget_p.cpp"
|
---|