OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / debugger / logwindow.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "logwindow.h"
35
36 #include "debuggeractions.h"
37 #include "debuggercore.h"
38
39 #include <QtCore/QDebug>
40 #include <QtCore/QFile>
41 #include <QtCore/QTime>
42
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>
52
53 #include <aggregation/aggregate.h>
54 #include <coreplugin/findplaceholder.h>
55 #include <coreplugin/minisplitter.h>
56 #include <find/basetextfind.h>
57
58 #include <utils/savedaction.h>
59
60 namespace Debugger {
61 namespace Internal {
62
63 static QChar charForChannel(int channel)
64 {
65     switch (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';
73         case LogMisc:
74         default: return ' ';
75     }
76 }
77
78 static LogChannel channelForChar(QChar c)
79 {
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;
89     }
90 }
91
92
93 /////////////////////////////////////////////////////////////////////
94 //
95 // OutputHighlighter
96 //
97 /////////////////////////////////////////////////////////////////////
98
99 class OutputHighlighter : public QSyntaxHighlighter
100 {
101 public:
102     OutputHighlighter(QPlainTextEdit *parent)
103         : QSyntaxHighlighter(parent->document()), m_parent(parent)
104     {}
105
106 private:
107     void highlightBlock(const QString &text)
108     {
109         QTextCharFormat format;
110         switch (channelForChar(text.isEmpty() ? QChar() : text.at(0))) {
111             case LogInput:
112                 format.setForeground(Qt::blue);
113                 setFormat(1, text.size(), format);
114                 break;
115             case LogStatus:
116                 format.setForeground(Qt::darkGreen);
117                 setFormat(1, text.size(), format);
118                 break;
119             case LogWarning:
120                 format.setForeground(Qt::darkYellow);
121                 setFormat(1, text.size(), format);
122                 break;
123             case LogError:
124                 format.setForeground(Qt::red);
125                 setFormat(1, text.size(), format);
126                 break;
127             case LogTime:
128                 format.setForeground(Qt::darkRed);
129                 setFormat(1, text.size(), format);
130                 break;
131             default:
132                 break;
133         }
134         QColor base = m_parent->palette().color(QPalette::Base);
135         format.setForeground(base);
136         format.setFontPointSize(1);
137         setFormat(0, 1, format);
138     }
139
140     QPlainTextEdit *m_parent;
141 };
142
143
144 /////////////////////////////////////////////////////////////////////
145 //
146 // InputHighlighter
147 //
148 /////////////////////////////////////////////////////////////////////
149
150 class InputHighlighter : public QSyntaxHighlighter
151 {
152 public:
153     InputHighlighter(QPlainTextEdit *parent)
154         : QSyntaxHighlighter(parent->document()), m_parent(parent)
155     {}
156
157 private:
158     void highlightBlock(const QString &text)
159     {
160         if (text.size() > 3 && text.at(2) == QLatin1Char(':')) {
161             QTextCharFormat format;
162             format.setForeground(Qt::darkRed);
163             setFormat(1, text.size(), format);
164         }
165     }
166
167     QPlainTextEdit *m_parent;
168 };
169
170
171 /////////////////////////////////////////////////////////////////////
172 //
173 // DebbuggerPane base class
174 //
175 /////////////////////////////////////////////////////////////////////
176
177 class DebuggerPane : public QPlainTextEdit
178 {
179     Q_OBJECT
180
181 public:
182     DebuggerPane(QWidget *parent)
183         : QPlainTextEdit(parent)
184     {
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()));
192
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()));
197     }
198
199     void contextMenuEvent(QContextMenuEvent *ev)
200     {
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());
212         delete menu;
213     }
214
215     virtual void addContextActions(QMenu *) {}
216
217 private slots:
218     void saveContents();
219
220 private:
221     QAction *m_clearContentsAction;
222     QAction *m_saveContentsAction;
223 };
224
225 void DebuggerPane::saveContents()
226 {
227     LogWindow::writeLogContents(this, this);
228 }
229
230 /////////////////////////////////////////////////////////////////////
231 //
232 // InputPane
233 //
234 /////////////////////////////////////////////////////////////////////
235
236 class InputPane : public DebuggerPane
237 {
238     Q_OBJECT
239 public:
240     InputPane(QWidget *parent)
241         : DebuggerPane(parent)
242     {
243         (void) new InputHighlighter(this);
244     }
245
246 signals:
247     void clearContentsRequested();
248     void statusMessageRequested(const QString &, int);
249     void commandSelected(int);
250
251 private:
252     void keyPressEvent(QKeyEvent *ev)
253     {
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();
258         else
259             QPlainTextEdit::keyPressEvent(ev);
260     }
261
262     void mouseDoubleClickEvent(QMouseEvent *ev)
263     {
264         QString line = cursorForPosition(ev->pos()).block().text();
265         int n = 0;
266
267         // cut time string
268         if (line.size() > 18 && line.at(0) == '[')
269             line = line.mid(18);
270         //qDebug() << line;
271
272         for (int i = 0; i != line.size(); ++i) {
273             QChar c = line.at(i);
274             if (!c.isDigit())
275                 break;
276             n = 10 * n + c.unicode() - '0';
277         }
278         emit commandSelected(n);
279     }
280
281     void addContextActions(QMenu *menu)
282     {
283        menu->addAction(debuggerCore()->action(ExecuteCommand));
284     }
285
286     void focusInEvent(QFocusEvent *ev)
287     {
288         emit statusMessageRequested(tr("Type Ctrl-<Return> to execute a line."), -1);
289         QPlainTextEdit::focusInEvent(ev);
290     }
291
292     void focusOutEvent(QFocusEvent *ev)
293     {
294         emit statusMessageRequested(QString(), -1);
295         QPlainTextEdit::focusOutEvent(ev);
296     }
297 };
298
299
300 /////////////////////////////////////////////////////////////////////
301 //
302 // CombinedPane
303 //
304 /////////////////////////////////////////////////////////////////////
305
306 class CombinedPane : public DebuggerPane
307 {
308     Q_OBJECT
309 public:
310     CombinedPane(QWidget *parent)
311         : DebuggerPane(parent)
312     {
313         (void) new OutputHighlighter(this);
314     }
315
316 public slots:
317     void gotoResult(int i)
318     {
319         QString needle = QString::number(i) + '^';
320         QString needle2 = QLatin1Char('>') + needle;
321         QTextCursor cursor(document());
322         do {
323             cursor = document()->find(needle, cursor);
324             if (cursor.isNull())
325                 break; // Not found.
326             const QString line = cursor.block().text();
327             if (line.startsWith(needle) || line.startsWith(needle2)) {
328                 setFocus();
329                 setTextCursor(cursor);
330                 ensureCursorVisible();
331                 cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
332                 setTextCursor(cursor);
333                 break;
334             }
335         } while (cursor.movePosition(QTextCursor::Down));
336     }
337 };
338
339
340 /////////////////////////////////////////////////////////////////////
341 //
342 // DebuggerOutputWindow
343 //
344 /////////////////////////////////////////////////////////////////////
345
346 LogWindow::LogWindow(QWidget *parent)
347   : QWidget(parent)
348 {
349     setWindowTitle(tr("Debugger Log"));
350     setObjectName("Log");
351
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);
359
360     // input only
361     m_inputText = new InputPane(this);
362     m_inputText->setReadOnly(false);
363     m_inputText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
364
365     m_splitter->addWidget(m_inputText);
366     m_splitter->addWidget(m_combinedText);
367     m_splitter->setStretchFactor(0, 1);
368     m_splitter->setStretchFactor(1, 3);
369
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));
375     setLayout(layout);
376
377     Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
378     aggregate->add(m_combinedText);
379     aggregate->add(new Find::BaseTextFind(m_combinedText));
380
381     aggregate = new Aggregation::Aggregate;
382     aggregate->add(m_inputText);
383     aggregate->add(new Find::BaseTextFind(m_inputText));
384
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)));
389 }
390
391 void LogWindow::showOutput(int channel, const QString &output)
392 {
393     if (output.isEmpty())
394         return;
395     QTextCursor oldCursor = m_combinedText->textCursor();
396     QTextCursor cursor = oldCursor;
397     cursor.movePosition(QTextCursor::End);
398     bool atEnd = oldCursor.position() == cursor.position();
399
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...
404         const int n = 30000;
405         if (line.size() > n) {
406             line.truncate(n);
407             line += QLatin1String(" [...] <cut off>");
408         }
409         if (line != QLatin1String("(gdb) "))
410             m_combinedText->appendPlainText(charForChannel(channel) + line);
411     }
412     cursor.movePosition(QTextCursor::End);
413     if (atEnd) {
414         m_combinedText->setTextCursor(cursor);
415         m_combinedText->ensureCursorVisible();
416     }
417 }
418
419 void LogWindow::showInput(int channel, const QString &input)
420 {
421     Q_UNUSED(channel)
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();
429 }
430
431 void LogWindow::clearContents()
432 {
433     m_combinedText->clear();
434     m_inputText->clear();
435 }
436
437 void LogWindow::setCursor(const QCursor &cursor)
438 {
439     m_combinedText->viewport()->setCursor(cursor);
440     m_inputText->viewport()->setCursor(cursor);
441     QWidget::setCursor(cursor);
442 }
443
444 QString LogWindow::combinedContents() const
445 {
446     return m_combinedText->toPlainText();
447 }
448
449 QString LogWindow::inputContents() const
450 {
451     return m_inputText->toPlainText();
452 }
453
454 QString LogWindow::logTimeStamp()
455 {
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);
461
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]");
472         return rc;
473     }
474     return lastTimeStamp;
475 }
476
477 bool LogWindow::writeLogContents(const QPlainTextEdit *editor, QWidget *parent)
478 {
479     bool success = false;
480     while (!success) {
481         const QString fileName = QFileDialog::getSaveFileName(parent, tr("Log File"));
482         if (fileName.isEmpty())
483             break;
484         QFile file(fileName);
485         if (file.open(QIODevice::WriteOnly|QIODevice::Text|QIODevice::Truncate)) {
486             file.write(editor->toPlainText().toUtf8());
487             file.close();
488             success = true;
489         } else {
490             QMessageBox::warning(parent, tr("Write Failure"),
491                                  tr("Unable to write log contents to '%1': %2").
492                                  arg(fileName, file.errorString()));
493         }
494     }
495     return success;
496 }
497
498 } // namespace Internal
499 } // namespace Debugger
500
501 #include "logwindow.moc"