1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "logwindow.h"
36 #include "debuggeractions.h"
37 #include "debuggercore.h"
39 #include <QtCore/QDebug>
40 #include <QtCore/QFile>
41 #include <QtCore/QTime>
43 #include <QtGui/QHBoxLayout>
44 #include <QtGui/QVBoxLayout>
45 #include <QtGui/QKeyEvent>
46 #include <QtGui/QMenu>
47 #include <QtGui/QSpacerItem>
48 #include <QtGui/QSyntaxHighlighter>
49 #include <QtGui/QPlainTextEdit>
50 #include <QtGui/QFileDialog>
51 #include <QtGui/QMessageBox>
53 #include <aggregation/aggregate.h>
54 #include <coreplugin/findplaceholder.h>
55 #include <coreplugin/minisplitter.h>
56 #include <find/basetextfind.h>
58 #include <utils/savedaction.h>
63 static QChar charForChannel(int channel)
66 case LogDebug: return 'd';
67 case LogWarning: return 'w';
68 case LogError: return 'e';
69 case LogInput: return '<';
70 case LogOutput: return '>';
71 case LogStatus: return 's';
72 case LogTime: return 't';
78 static LogChannel channelForChar(QChar c)
80 switch (c.unicode()) {
81 case 'd': return LogDebug;
82 case 'w': return LogWarning;
83 case 'e': return LogError;
84 case '<': return LogInput;
85 case '>': return LogOutput;
86 case 's': return LogStatus;
87 case 't': return LogTime;
88 default: return LogMisc;
93 /////////////////////////////////////////////////////////////////////
97 /////////////////////////////////////////////////////////////////////
99 class OutputHighlighter : public QSyntaxHighlighter
102 OutputHighlighter(QPlainTextEdit *parent)
103 : QSyntaxHighlighter(parent->document()), m_parent(parent)
107 void highlightBlock(const QString &text)
109 QTextCharFormat format;
110 switch (channelForChar(text.isEmpty() ? QChar() : text.at(0))) {
112 format.setForeground(Qt::blue);
113 setFormat(1, text.size(), format);
116 format.setForeground(Qt::darkGreen);
117 setFormat(1, text.size(), format);
120 format.setForeground(Qt::darkYellow);
121 setFormat(1, text.size(), format);
124 format.setForeground(Qt::red);
125 setFormat(1, text.size(), format);
128 format.setForeground(Qt::darkRed);
129 setFormat(1, text.size(), format);
134 QColor base = m_parent->palette().color(QPalette::Base);
135 format.setForeground(base);
136 format.setFontPointSize(1);
137 setFormat(0, 1, format);
140 QPlainTextEdit *m_parent;
144 /////////////////////////////////////////////////////////////////////
148 /////////////////////////////////////////////////////////////////////
150 class InputHighlighter : public QSyntaxHighlighter
153 InputHighlighter(QPlainTextEdit *parent)
154 : QSyntaxHighlighter(parent->document()), m_parent(parent)
158 void highlightBlock(const QString &text)
160 if (text.size() > 3 && text.at(2) == QLatin1Char(':')) {
161 QTextCharFormat format;
162 format.setForeground(Qt::darkRed);
163 setFormat(1, text.size(), format);
167 QPlainTextEdit *m_parent;
171 /////////////////////////////////////////////////////////////////////
173 // DebbuggerPane base class
175 /////////////////////////////////////////////////////////////////////
177 class DebuggerPane : public QPlainTextEdit
182 DebuggerPane(QWidget *parent)
183 : QPlainTextEdit(parent)
185 setMaximumBlockCount(100000);
186 setFrameStyle(QFrame::NoFrame);
187 m_clearContentsAction = new QAction(this);
188 m_clearContentsAction->setText(tr("Clear Contents"));
189 m_clearContentsAction->setEnabled(true);
190 connect(m_clearContentsAction, SIGNAL(triggered(bool)),
191 parent, SLOT(clearContents()));
193 m_saveContentsAction = new QAction(this);
194 m_saveContentsAction->setText(tr("Save Contents"));
195 m_saveContentsAction->setEnabled(true);
196 connect(m_saveContentsAction, SIGNAL(triggered()), this, SLOT(saveContents()));
199 void contextMenuEvent(QContextMenuEvent *ev)
201 QMenu *menu = createStandardContextMenu();
202 menu->addAction(m_clearContentsAction);
203 menu->addAction(m_saveContentsAction); // X11 clipboard is unreliable for long texts
204 addContextActions(menu);
205 debuggerCore()->action(ExecuteCommand)->setData(textCursor().block().text());
206 menu->addAction(debuggerCore()->action(ExecuteCommand));
207 menu->addAction(debuggerCore()->action(LogTimeStamps));
208 menu->addAction(debuggerCore()->action(VerboseLog));
209 menu->addSeparator();
210 menu->addAction(debuggerCore()->action(SettingsDialog));
211 menu->exec(ev->globalPos());
215 virtual void addContextActions(QMenu *) {}
221 QAction *m_clearContentsAction;
222 QAction *m_saveContentsAction;
225 void DebuggerPane::saveContents()
227 LogWindow::writeLogContents(this, this);
230 /////////////////////////////////////////////////////////////////////
234 /////////////////////////////////////////////////////////////////////
236 class InputPane : public DebuggerPane
240 InputPane(QWidget *parent)
241 : DebuggerPane(parent)
243 (void) new InputHighlighter(this);
247 void clearContentsRequested();
248 void statusMessageRequested(const QString &, int);
249 void commandSelected(int);
252 void keyPressEvent(QKeyEvent *ev)
254 if (ev->modifiers() == Qt::ControlModifier && ev->key() == Qt::Key_Return)
255 debuggerCore()->action(ExecuteCommand)->trigger(textCursor().block().text());
256 else if (ev->modifiers() == Qt::ControlModifier && ev->key() == Qt::Key_R)
257 emit clearContentsRequested();
259 QPlainTextEdit::keyPressEvent(ev);
262 void mouseDoubleClickEvent(QMouseEvent *ev)
264 QString line = cursorForPosition(ev->pos()).block().text();
268 if (line.size() > 18 && line.at(0) == '[')
272 for (int i = 0; i != line.size(); ++i) {
273 QChar c = line.at(i);
276 n = 10 * n + c.unicode() - '0';
278 emit commandSelected(n);
281 void addContextActions(QMenu *menu)
283 menu->addAction(debuggerCore()->action(ExecuteCommand));
286 void focusInEvent(QFocusEvent *ev)
288 emit statusMessageRequested(tr("Type Ctrl-<Return> to execute a line."), -1);
289 QPlainTextEdit::focusInEvent(ev);
292 void focusOutEvent(QFocusEvent *ev)
294 emit statusMessageRequested(QString(), -1);
295 QPlainTextEdit::focusOutEvent(ev);
300 /////////////////////////////////////////////////////////////////////
304 /////////////////////////////////////////////////////////////////////
306 class CombinedPane : public DebuggerPane
310 CombinedPane(QWidget *parent)
311 : DebuggerPane(parent)
313 (void) new OutputHighlighter(this);
317 void gotoResult(int i)
319 QString needle = QString::number(i) + '^';
320 QString needle2 = QLatin1Char('>') + needle;
321 QTextCursor cursor(document());
323 cursor = document()->find(needle, cursor);
326 const QString line = cursor.block().text();
327 if (line.startsWith(needle) || line.startsWith(needle2)) {
329 setTextCursor(cursor);
330 ensureCursorVisible();
331 cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
332 setTextCursor(cursor);
335 } while (cursor.movePosition(QTextCursor::Down));
340 /////////////////////////////////////////////////////////////////////
342 // DebuggerOutputWindow
344 /////////////////////////////////////////////////////////////////////
346 LogWindow::LogWindow(QWidget *parent)
349 setWindowTitle(tr("Debugger Log"));
350 setObjectName("Log");
352 QSplitter *m_splitter = new Core::MiniSplitter(Qt::Horizontal);
353 m_splitter->setParent(this);
354 // mixed input/output
355 m_combinedText = new CombinedPane(this);
356 m_combinedText->setReadOnly(true);
357 m_combinedText->setReadOnly(false);
358 m_combinedText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
361 m_inputText = new InputPane(this);
362 m_inputText->setReadOnly(false);
363 m_inputText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
365 m_splitter->addWidget(m_inputText);
366 m_splitter->addWidget(m_combinedText);
367 m_splitter->setStretchFactor(0, 1);
368 m_splitter->setStretchFactor(1, 3);
370 QVBoxLayout *layout = new QVBoxLayout(this);
371 layout->setMargin(0);
372 layout->setSpacing(0);
373 layout->addWidget(m_splitter);
374 layout->addWidget(new Core::FindToolBarPlaceHolder(this));
377 Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
378 aggregate->add(m_combinedText);
379 aggregate->add(new Find::BaseTextFind(m_combinedText));
381 aggregate = new Aggregation::Aggregate;
382 aggregate->add(m_inputText);
383 aggregate->add(new Find::BaseTextFind(m_inputText));
385 connect(m_inputText, SIGNAL(statusMessageRequested(QString,int)),
386 this, SIGNAL(statusMessageRequested(QString,int)));
387 connect(m_inputText, SIGNAL(commandSelected(int)),
388 m_combinedText, SLOT(gotoResult(int)));
391 void LogWindow::showOutput(int channel, const QString &output)
393 if (output.isEmpty())
395 QTextCursor oldCursor = m_combinedText->textCursor();
396 QTextCursor cursor = oldCursor;
397 cursor.movePosition(QTextCursor::End);
398 bool atEnd = oldCursor.position() == cursor.position();
400 if (debuggerCore()->boolSetting(LogTimeStamps))
401 m_combinedText->appendPlainText(charForChannel(LogTime) + logTimeStamp());
402 foreach (QString line, output.split('\n')) {
403 // FIXME: QTextEdit asserts on really long lines...
405 if (line.size() > n) {
407 line += QLatin1String(" [...] <cut off>");
409 if (line != QLatin1String("(gdb) "))
410 m_combinedText->appendPlainText(charForChannel(channel) + line);
412 cursor.movePosition(QTextCursor::End);
414 m_combinedText->setTextCursor(cursor);
415 m_combinedText->ensureCursorVisible();
419 void LogWindow::showInput(int channel, const QString &input)
422 if (debuggerCore()->boolSetting(LogTimeStamps))
423 m_inputText->appendPlainText(logTimeStamp());
424 m_inputText->appendPlainText(input);
425 QTextCursor cursor = m_inputText->textCursor();
426 cursor.movePosition(QTextCursor::End);
427 m_inputText->setTextCursor(cursor);
428 m_inputText->ensureCursorVisible();
431 void LogWindow::clearContents()
433 m_combinedText->clear();
434 m_inputText->clear();
437 void LogWindow::setCursor(const QCursor &cursor)
439 m_combinedText->viewport()->setCursor(cursor);
440 m_inputText->viewport()->setCursor(cursor);
441 QWidget::setCursor(cursor);
444 QString LogWindow::combinedContents() const
446 return m_combinedText->toPlainText();
449 QString LogWindow::inputContents() const
451 return m_inputText->toPlainText();
454 QString LogWindow::logTimeStamp()
456 // Cache the last log time entry by ms. If time progresses,
457 // report the difference to the last time stamp in ms.
458 static const QString logTimeFormat(QLatin1String("hh:mm:ss.zzz"));
459 static QTime lastTime = QTime::currentTime();
460 static QString lastTimeStamp = lastTime.toString(logTimeFormat);
462 const QTime currentTime = QTime::currentTime();
463 if (currentTime != lastTime) {
464 const int elapsedMS = lastTime.msecsTo(currentTime);
465 lastTime = currentTime;
466 lastTimeStamp = lastTime.toString(logTimeFormat);
467 // Append time elapsed
468 QString rc = lastTimeStamp;
469 rc += QLatin1String(" [");
470 rc += QString::number(elapsedMS);
471 rc += QLatin1String("ms]");
474 return lastTimeStamp;
477 bool LogWindow::writeLogContents(const QPlainTextEdit *editor, QWidget *parent)
479 bool success = false;
481 const QString fileName = QFileDialog::getSaveFileName(parent, tr("Log File"));
482 if (fileName.isEmpty())
484 QFile file(fileName);
485 if (file.open(QIODevice::WriteOnly|QIODevice::Text|QIODevice::Truncate)) {
486 file.write(editor->toPlainText().toUtf8());
490 QMessageBox::warning(parent, tr("Write Failure"),
491 tr("Unable to write log contents to '%1': %2").
492 arg(fileName, file.errorString()));
498 } // namespace Internal
499 } // namespace Debugger
501 #include "logwindow.moc"