2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
|
|
|
|
|
** Contact: http://www.qt-project.org/legal
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** This file is part of Qt Creator.
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
|
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
|
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
|
|
|
|
** GNU Lesser General Public License Usage
|
2012-10-02 09:12:39 +02:00
|
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
|
|
|
** General Public License version 2.1 as published by the Free Software
|
|
|
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
|
|
|
** packaging of this file. Please review the following information to
|
|
|
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
|
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
|
|
|
**
|
|
|
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
|
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
2011-04-14 10:39:09 +02:00
|
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
|
|
|
**
|
2012-10-02 09:12:39 +02:00
|
|
|
****************************************************************************/
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
#include "outputwindow.h"
|
|
|
|
|
|
2011-04-21 13:42:17 +02:00
|
|
|
#include "actionmanager/actionmanager.h"
|
|
|
|
|
#include "actionmanager/command.h"
|
|
|
|
|
#include "coreconstants.h"
|
|
|
|
|
#include "icore.h"
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
#include <utils/qtcassert.h>
|
|
|
|
|
#include <utils/outputformatter.h>
|
|
|
|
|
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QAction>
|
|
|
|
|
#include <QScrollBar>
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
2011-04-21 13:42:17 +02:00
|
|
|
namespace Core {
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
/*******************/
|
|
|
|
|
|
|
|
|
|
OutputWindow::OutputWindow(Core::Context context, QWidget *parent)
|
|
|
|
|
: QPlainTextEdit(parent)
|
|
|
|
|
, m_formatter(0)
|
|
|
|
|
, m_enforceNewline(false)
|
|
|
|
|
, m_scrollToBottom(false)
|
|
|
|
|
, m_linksActive(true)
|
|
|
|
|
, m_mousePressed(false)
|
2011-04-29 09:36:04 +02:00
|
|
|
, m_maxLineCount(100000)
|
2011-04-14 10:39:09 +02:00
|
|
|
{
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
|
|
|
//setCenterOnScroll(false);
|
|
|
|
|
setFrameShape(QFrame::NoFrame);
|
|
|
|
|
setMouseTracking(true);
|
|
|
|
|
|
|
|
|
|
m_outputWindowContext = new Core::IContext;
|
|
|
|
|
m_outputWindowContext->setContext(context);
|
|
|
|
|
m_outputWindowContext->setWidget(this);
|
2012-01-24 15:36:40 +01:00
|
|
|
ICore::addContextObject(m_outputWindowContext);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
QAction *undoAction = new QAction(this);
|
|
|
|
|
QAction *redoAction = new QAction(this);
|
|
|
|
|
QAction *cutAction = new QAction(this);
|
|
|
|
|
QAction *copyAction = new QAction(this);
|
|
|
|
|
QAction *pasteAction = new QAction(this);
|
|
|
|
|
QAction *selectAllAction = new QAction(this);
|
|
|
|
|
|
2012-05-24 13:49:06 +02:00
|
|
|
ActionManager::registerAction(undoAction, Core::Constants::UNDO, context);
|
|
|
|
|
ActionManager::registerAction(redoAction, Core::Constants::REDO, context);
|
|
|
|
|
ActionManager::registerAction(cutAction, Core::Constants::CUT, context);
|
|
|
|
|
ActionManager::registerAction(copyAction, Core::Constants::COPY, context);
|
|
|
|
|
ActionManager::registerAction(pasteAction, Core::Constants::PASTE, context);
|
|
|
|
|
ActionManager::registerAction(selectAllAction, Core::Constants::SELECTALL, context);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
connect(undoAction, SIGNAL(triggered()), this, SLOT(undo()));
|
|
|
|
|
connect(redoAction, SIGNAL(triggered()), this, SLOT(redo()));
|
|
|
|
|
connect(cutAction, SIGNAL(triggered()), this, SLOT(cut()));
|
|
|
|
|
connect(copyAction, SIGNAL(triggered()), this, SLOT(copy()));
|
|
|
|
|
connect(pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
|
|
|
|
|
connect(selectAllAction, SIGNAL(triggered()), this, SLOT(selectAll()));
|
|
|
|
|
|
|
|
|
|
connect(this, SIGNAL(undoAvailable(bool)), undoAction, SLOT(setEnabled(bool)));
|
|
|
|
|
connect(this, SIGNAL(redoAvailable(bool)), redoAction, SLOT(setEnabled(bool)));
|
|
|
|
|
connect(this, SIGNAL(copyAvailable(bool)), cutAction, SLOT(setEnabled(bool))); // OutputWindow never read-only
|
|
|
|
|
connect(this, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool)));
|
|
|
|
|
|
|
|
|
|
undoAction->setEnabled(false);
|
|
|
|
|
redoAction->setEnabled(false);
|
|
|
|
|
cutAction->setEnabled(false);
|
|
|
|
|
copyAction->setEnabled(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutputWindow::~OutputWindow()
|
|
|
|
|
{
|
2012-01-24 15:36:40 +01:00
|
|
|
Core::ICore::removeContextObject(m_outputWindowContext);
|
2011-04-14 10:39:09 +02:00
|
|
|
delete m_outputWindowContext;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::mousePressEvent(QMouseEvent * e)
|
|
|
|
|
{
|
|
|
|
|
m_mousePressed = true;
|
|
|
|
|
QPlainTextEdit::mousePressEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
m_mousePressed = false;
|
|
|
|
|
|
|
|
|
|
if (m_linksActive) {
|
|
|
|
|
const QString href = anchorAt(e->pos());
|
|
|
|
|
if (m_formatter)
|
|
|
|
|
m_formatter->handleLink(href);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mouse was released, activate links again
|
|
|
|
|
m_linksActive = true;
|
|
|
|
|
|
|
|
|
|
QPlainTextEdit::mouseReleaseEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::mouseMoveEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
// Cursor was dragged to make a selection, deactivate links
|
|
|
|
|
if (m_mousePressed && textCursor().hasSelection())
|
|
|
|
|
m_linksActive = false;
|
|
|
|
|
|
|
|
|
|
if (!m_linksActive || anchorAt(e->pos()).isEmpty())
|
|
|
|
|
viewport()->setCursor(Qt::IBeamCursor);
|
|
|
|
|
else
|
|
|
|
|
viewport()->setCursor(Qt::PointingHandCursor);
|
|
|
|
|
QPlainTextEdit::mouseMoveEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::resizeEvent(QResizeEvent *e)
|
|
|
|
|
{
|
|
|
|
|
//Keep scrollbar at bottom of window while resizing, to ensure we keep scrolling
|
|
|
|
|
//This can happen if window is resized while building, or if the horizontal scrollbar appears
|
|
|
|
|
bool atBottom = isScrollbarAtBottom();
|
|
|
|
|
QPlainTextEdit::resizeEvent(e);
|
|
|
|
|
if (atBottom)
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::keyPressEvent(QKeyEvent *ev)
|
|
|
|
|
{
|
|
|
|
|
QPlainTextEdit::keyPressEvent(ev);
|
|
|
|
|
|
|
|
|
|
//Ensure we scroll also on Ctrl+Home or Ctrl+End
|
|
|
|
|
if (ev->matches(QKeySequence::MoveToStartOfDocument))
|
|
|
|
|
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMinimum);
|
|
|
|
|
else if (ev->matches(QKeySequence::MoveToEndOfDocument))
|
|
|
|
|
verticalScrollBar()->triggerAction(QAbstractSlider::SliderToMaximum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutputFormatter *OutputWindow::formatter() const
|
|
|
|
|
{
|
|
|
|
|
return m_formatter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setFormatter(OutputFormatter *formatter)
|
|
|
|
|
{
|
|
|
|
|
m_formatter = formatter;
|
|
|
|
|
m_formatter->setPlainTextEdit(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::showEvent(QShowEvent *e)
|
|
|
|
|
{
|
|
|
|
|
QPlainTextEdit::showEvent(e);
|
|
|
|
|
if (m_scrollToBottom) {
|
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
|
|
|
|
}
|
|
|
|
|
m_scrollToBottom = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString OutputWindow::doNewlineEnfocement(const QString &out)
|
|
|
|
|
{
|
|
|
|
|
m_scrollToBottom = true;
|
|
|
|
|
QString s = out;
|
|
|
|
|
if (m_enforceNewline) {
|
|
|
|
|
s.prepend(QLatin1Char('\n'));
|
|
|
|
|
m_enforceNewline = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s.endsWith(QLatin1Char('\n'))) {
|
|
|
|
|
m_enforceNewline = true; // make appendOutputInline put in a newline next time
|
|
|
|
|
s.chop(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-29 09:36:04 +02:00
|
|
|
void OutputWindow::setMaxLineCount(int count)
|
|
|
|
|
{
|
|
|
|
|
m_maxLineCount = count;
|
|
|
|
|
setMaximumBlockCount(m_maxLineCount);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-14 10:39:09 +02:00
|
|
|
void OutputWindow::appendMessage(const QString &output, OutputFormat format)
|
|
|
|
|
{
|
|
|
|
|
QString out = output;
|
|
|
|
|
out.remove(QLatin1Char('\r'));
|
2011-04-29 09:36:04 +02:00
|
|
|
setMaximumBlockCount(m_maxLineCount);
|
2011-04-14 10:39:09 +02:00
|
|
|
const bool atBottom = isScrollbarAtBottom();
|
|
|
|
|
|
|
|
|
|
if (format == ErrorMessageFormat || format == NormalMessageFormat) {
|
|
|
|
|
|
|
|
|
|
m_formatter->appendMessage(doNewlineEnfocement(out), format);
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
bool sameLine = format == StdOutFormatSameLine
|
|
|
|
|
|| format == StdErrFormatSameLine;
|
|
|
|
|
|
|
|
|
|
if (sameLine) {
|
|
|
|
|
m_scrollToBottom = true;
|
|
|
|
|
|
|
|
|
|
int newline = -1;
|
|
|
|
|
bool enforceNewline = m_enforceNewline;
|
|
|
|
|
m_enforceNewline = false;
|
|
|
|
|
|
|
|
|
|
if (!enforceNewline) {
|
|
|
|
|
newline = out.indexOf(QLatin1Char('\n'));
|
|
|
|
|
moveCursor(QTextCursor::End);
|
|
|
|
|
if (newline != -1)
|
|
|
|
|
m_formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString s = out.mid(newline+1);
|
|
|
|
|
if (s.isEmpty()) {
|
|
|
|
|
m_enforceNewline = true;
|
|
|
|
|
} else {
|
|
|
|
|
if (s.endsWith(QLatin1Char('\n'))) {
|
|
|
|
|
m_enforceNewline = true;
|
|
|
|
|
s.chop(1);
|
|
|
|
|
}
|
|
|
|
|
m_formatter->appendMessage(QLatin1Char('\n') + s, format);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
m_formatter->appendMessage(doNewlineEnfocement(out), format);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (atBottom)
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
enableUndoRedo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO rename
|
2011-04-29 09:36:04 +02:00
|
|
|
void OutputWindow::appendText(const QString &textIn, const QTextCharFormat &format)
|
2011-04-14 10:39:09 +02:00
|
|
|
{
|
|
|
|
|
QString text = textIn;
|
|
|
|
|
text.remove(QLatin1Char('\r'));
|
2011-04-29 09:36:04 +02:00
|
|
|
if (m_maxLineCount > 0 && document()->blockCount() > m_maxLineCount)
|
2011-04-14 10:39:09 +02:00
|
|
|
return;
|
|
|
|
|
const bool atBottom = isScrollbarAtBottom();
|
|
|
|
|
QTextCursor cursor = QTextCursor(document());
|
|
|
|
|
cursor.movePosition(QTextCursor::End);
|
|
|
|
|
cursor.beginEditBlock();
|
|
|
|
|
cursor.insertText(doNewlineEnfocement(text), format);
|
|
|
|
|
|
2011-04-29 09:36:04 +02:00
|
|
|
if (m_maxLineCount > 0 && document()->blockCount() > m_maxLineCount) {
|
2011-04-14 10:39:09 +02:00
|
|
|
QTextCharFormat tmp;
|
|
|
|
|
tmp.setFontWeight(QFont::Bold);
|
|
|
|
|
cursor.insertText(tr("Additional output omitted\n"), tmp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cursor.endEditBlock();
|
|
|
|
|
if (atBottom)
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OutputWindow::isScrollbarAtBottom() const
|
|
|
|
|
{
|
|
|
|
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::clear()
|
|
|
|
|
{
|
|
|
|
|
m_enforceNewline = false;
|
|
|
|
|
QPlainTextEdit::clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::scrollToBottom()
|
|
|
|
|
{
|
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
2011-04-21 13:42:17 +02:00
|
|
|
// QPlainTextEdit destroys the first calls value in case of multiline
|
|
|
|
|
// text, so make sure that the scroll bar actually gets the value set.
|
|
|
|
|
// Is a noop if the first call succeeded.
|
|
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::grayOutOldContent()
|
|
|
|
|
{
|
|
|
|
|
QTextCursor cursor = textCursor();
|
|
|
|
|
cursor.movePosition(QTextCursor::End);
|
|
|
|
|
QTextCharFormat endFormat = cursor.charFormat();
|
|
|
|
|
|
|
|
|
|
cursor.select(QTextCursor::Document);
|
|
|
|
|
|
|
|
|
|
QTextCharFormat format;
|
|
|
|
|
const QColor bkgColor = palette().base().color();
|
|
|
|
|
const QColor fgdColor = palette().text().color();
|
|
|
|
|
double bkgFactor = 0.50;
|
|
|
|
|
double fgdFactor = 1.-bkgFactor;
|
|
|
|
|
format.setForeground(QColor((bkgFactor * bkgColor.red() + fgdFactor * fgdColor.red()),
|
|
|
|
|
(bkgFactor * bkgColor.green() + fgdFactor * fgdColor.green()),
|
|
|
|
|
(bkgFactor * bkgColor.blue() + fgdFactor * fgdColor.blue()) ));
|
|
|
|
|
cursor.mergeCharFormat(format);
|
|
|
|
|
|
|
|
|
|
cursor.movePosition(QTextCursor::End);
|
|
|
|
|
cursor.setCharFormat(endFormat);
|
|
|
|
|
cursor.insertBlock(QTextBlockFormat());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::enableUndoRedo()
|
|
|
|
|
{
|
|
|
|
|
setMaximumBlockCount(0);
|
|
|
|
|
setUndoRedoEnabled(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setWordWrapEnabled(bool wrap)
|
|
|
|
|
{
|
|
|
|
|
if (wrap)
|
|
|
|
|
setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
|
|
|
|
|
else
|
|
|
|
|
setWordWrapMode(QTextOption::NoWrap);
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-21 13:42:17 +02:00
|
|
|
} // namespace Core
|