2012-10-02 09:12:39 +02:00
|
|
|
/****************************************************************************
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
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
|
2016-01-15 14:57:40 +01:00
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
2016-01-15 14:57:40 +01:00
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
2011-04-14 10:39:09 +02:00
|
|
|
**
|
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 "coreconstants.h"
|
|
|
|
|
#include "icore.h"
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2015-02-24 22:39:14 +02:00
|
|
|
#include <utils/outputformatter.h>
|
2013-08-02 12:15:04 +03:00
|
|
|
#include <utils/synchronousprocess.h>
|
|
|
|
|
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QAction>
|
2020-01-08 14:33:02 +01:00
|
|
|
#include <QCursor>
|
|
|
|
|
#include <QMimeData>
|
2019-12-18 13:49:10 +01:00
|
|
|
#include <QPointer>
|
2018-08-20 14:16:46 +02:00
|
|
|
#include <QRegularExpression>
|
2012-02-15 10:42:41 +01:00
|
|
|
#include <QScrollBar>
|
2018-09-25 10:50:32 +02:00
|
|
|
#include <QTextBlock>
|
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
|
|
|
|
2015-02-24 22:39:14 +02:00
|
|
|
namespace Internal {
|
|
|
|
|
|
|
|
|
|
class OutputWindowPrivate
|
|
|
|
|
{
|
|
|
|
|
public:
|
2020-02-17 14:46:04 +01:00
|
|
|
explicit OutputWindowPrivate(QTextDocument *document)
|
2016-08-01 09:27:42 +03:00
|
|
|
: cursor(document)
|
2015-02-24 22:39:14 +02:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~OutputWindowPrivate()
|
|
|
|
|
{
|
|
|
|
|
ICore::removeContextObject(outputWindowContext);
|
|
|
|
|
delete outputWindowContext;
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-01 09:27:42 +03:00
|
|
|
IContext *outputWindowContext = nullptr;
|
2019-12-18 13:49:10 +01:00
|
|
|
QPointer<Utils::OutputFormatter> formatter;
|
2019-05-24 14:52:19 +02:00
|
|
|
QString settingsKey;
|
2020-03-13 15:53:06 +01:00
|
|
|
OutputFormatter defaultFormatter;
|
2015-02-24 22:39:14 +02:00
|
|
|
|
2016-08-01 09:27:42 +03:00
|
|
|
bool enforceNewline = false;
|
2019-12-03 16:28:56 +01:00
|
|
|
bool prependCarriageReturn = false;
|
2016-08-01 09:27:42 +03:00
|
|
|
bool scrollToBottom = true;
|
|
|
|
|
bool linksActive = true;
|
2019-05-24 14:52:19 +02:00
|
|
|
bool zoomEnabled = false;
|
|
|
|
|
float originalFontSize = 0.;
|
|
|
|
|
bool originalReadOnly = false;
|
2018-09-25 13:58:24 +02:00
|
|
|
int maxCharCount = Core::Constants::DEFAULT_MAX_CHAR_COUNT;
|
2019-03-10 17:59:47 +01:00
|
|
|
Qt::MouseButton mouseButtonPressed = Qt::NoButton;
|
2015-02-24 22:45:44 +02:00
|
|
|
QTextCursor cursor;
|
2018-08-20 14:16:46 +02:00
|
|
|
QString filterText;
|
2019-06-21 12:25:07 +02:00
|
|
|
int lastFilteredBlockNumber = -1;
|
2019-05-14 14:33:08 +02:00
|
|
|
QPalette originalPalette;
|
2018-08-20 14:16:46 +02:00
|
|
|
OutputWindow::FilterModeFlags filterMode = OutputWindow::FilterModeFlag::Default;
|
2015-02-24 22:39:14 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace Internal
|
|
|
|
|
|
2011-04-14 10:39:09 +02:00
|
|
|
/*******************/
|
|
|
|
|
|
2019-03-31 08:04:43 +02:00
|
|
|
OutputWindow::OutputWindow(Context context, const QString &settingsKey, QWidget *parent)
|
2011-04-14 10:39:09 +02:00
|
|
|
: QPlainTextEdit(parent)
|
2015-02-24 22:45:44 +02:00
|
|
|
, d(new Internal::OutputWindowPrivate(document()))
|
2011-04-14 10:39:09 +02:00
|
|
|
{
|
|
|
|
|
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
|
|
|
|
//setCenterOnScroll(false);
|
|
|
|
|
setFrameShape(QFrame::NoFrame);
|
|
|
|
|
setMouseTracking(true);
|
2012-12-11 17:37:02 +01:00
|
|
|
setUndoRedoEnabled(false);
|
2020-03-13 15:53:06 +01:00
|
|
|
setFormatter(&d->defaultFormatter);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2019-05-24 14:52:19 +02:00
|
|
|
d->settingsKey = settingsKey;
|
2019-05-15 15:04:06 +02:00
|
|
|
|
2015-02-24 22:39:14 +02:00
|
|
|
d->outputWindowContext = new IContext;
|
|
|
|
|
d->outputWindowContext->setContext(context);
|
|
|
|
|
d->outputWindowContext->setWidget(this);
|
|
|
|
|
ICore::addContextObject(d->outputWindowContext);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2018-07-21 21:11:46 +02:00
|
|
|
auto undoAction = new QAction(this);
|
|
|
|
|
auto redoAction = new QAction(this);
|
|
|
|
|
auto cutAction = new QAction(this);
|
|
|
|
|
auto copyAction = new QAction(this);
|
|
|
|
|
auto pasteAction = new QAction(this);
|
|
|
|
|
auto selectAllAction = new QAction(this);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2014-11-16 10:52:41 +02:00
|
|
|
ActionManager::registerAction(undoAction, Constants::UNDO, context);
|
|
|
|
|
ActionManager::registerAction(redoAction, Constants::REDO, context);
|
|
|
|
|
ActionManager::registerAction(cutAction, Constants::CUT, context);
|
|
|
|
|
ActionManager::registerAction(copyAction, Constants::COPY, context);
|
|
|
|
|
ActionManager::registerAction(pasteAction, Constants::PASTE, context);
|
|
|
|
|
ActionManager::registerAction(selectAllAction, Constants::SELECTALL, context);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2016-02-02 09:10:54 +02:00
|
|
|
connect(undoAction, &QAction::triggered, this, &QPlainTextEdit::undo);
|
|
|
|
|
connect(redoAction, &QAction::triggered, this, &QPlainTextEdit::redo);
|
|
|
|
|
connect(cutAction, &QAction::triggered, this, &QPlainTextEdit::cut);
|
|
|
|
|
connect(copyAction, &QAction::triggered, this, &QPlainTextEdit::copy);
|
|
|
|
|
connect(pasteAction, &QAction::triggered, this, &QPlainTextEdit::paste);
|
|
|
|
|
connect(selectAllAction, &QAction::triggered, this, &QPlainTextEdit::selectAll);
|
2019-09-30 09:57:40 +02:00
|
|
|
connect(this, &QPlainTextEdit::blockCountChanged, this, [this] {
|
|
|
|
|
if (!d->filterText.isEmpty())
|
|
|
|
|
filterNewContent();
|
|
|
|
|
});
|
2016-02-02 09:10:54 +02:00
|
|
|
|
|
|
|
|
connect(this, &QPlainTextEdit::undoAvailable, undoAction, &QAction::setEnabled);
|
|
|
|
|
connect(this, &QPlainTextEdit::redoAvailable, redoAction, &QAction::setEnabled);
|
|
|
|
|
connect(this, &QPlainTextEdit::copyAvailable, cutAction, &QAction::setEnabled); // OutputWindow never read-only
|
|
|
|
|
connect(this, &QPlainTextEdit::copyAvailable, copyAction, &QAction::setEnabled);
|
2019-03-31 08:04:43 +02:00
|
|
|
connect(Core::ICore::instance(), &Core::ICore::saveSettingsRequested, this, [this] {
|
2019-05-24 14:52:19 +02:00
|
|
|
if (!d->settingsKey.isEmpty())
|
|
|
|
|
Core::ICore::settings()->setValue(d->settingsKey, fontZoom());
|
2019-03-31 08:04:43 +02:00
|
|
|
});
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
undoAction->setEnabled(false);
|
|
|
|
|
redoAction->setEnabled(false);
|
|
|
|
|
cutAction->setEnabled(false);
|
|
|
|
|
copyAction->setEnabled(false);
|
2015-04-28 17:24:52 +02:00
|
|
|
|
|
|
|
|
m_scrollTimer.setInterval(10);
|
|
|
|
|
m_scrollTimer.setSingleShot(true);
|
|
|
|
|
connect(&m_scrollTimer, &QTimer::timeout,
|
|
|
|
|
this, &OutputWindow::scrollToBottom);
|
|
|
|
|
m_lastMessage.start();
|
2015-08-17 16:29:09 +02:00
|
|
|
|
2019-05-24 14:52:19 +02:00
|
|
|
d->originalFontSize = font().pointSizeF();
|
2019-03-31 08:04:43 +02:00
|
|
|
|
2019-05-24 14:52:19 +02:00
|
|
|
if (!d->settingsKey.isEmpty()) {
|
|
|
|
|
float zoom = Core::ICore::settings()->value(d->settingsKey).toFloat();
|
2019-03-31 08:04:43 +02:00
|
|
|
setFontZoom(zoom);
|
|
|
|
|
}
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OutputWindow::~OutputWindow()
|
|
|
|
|
{
|
2015-02-24 22:39:14 +02:00
|
|
|
delete d;
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-17 14:46:04 +01:00
|
|
|
void OutputWindow::mousePressEvent(QMouseEvent *e)
|
2011-04-14 10:39:09 +02:00
|
|
|
{
|
2017-08-23 08:17:48 +03:00
|
|
|
d->mouseButtonPressed = e->button();
|
2011-04-14 10:39:09 +02:00
|
|
|
QPlainTextEdit::mousePressEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::mouseReleaseEvent(QMouseEvent *e)
|
|
|
|
|
{
|
2017-08-23 08:17:48 +03:00
|
|
|
if (d->linksActive && d->mouseButtonPressed == Qt::LeftButton) {
|
2011-04-14 10:39:09 +02:00
|
|
|
const QString href = anchorAt(e->pos());
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->handleLink(href);
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mouse was released, activate links again
|
2015-02-24 22:39:14 +02:00
|
|
|
d->linksActive = true;
|
2017-08-23 08:17:48 +03:00
|
|
|
d->mouseButtonPressed = Qt::NoButton;
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
QPlainTextEdit::mouseReleaseEvent(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::mouseMoveEvent(QMouseEvent *e)
|
|
|
|
|
{
|
|
|
|
|
// Cursor was dragged to make a selection, deactivate links
|
2017-08-23 08:17:48 +03:00
|
|
|
if (d->mouseButtonPressed != Qt::NoButton && textCursor().hasSelection())
|
2015-02-24 22:39:14 +02:00
|
|
|
d->linksActive = false;
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2015-02-24 22:39:14 +02:00
|
|
|
if (!d->linksActive || anchorAt(e->pos()).isEmpty())
|
2011-04-14 10:39:09 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setFormatter(OutputFormatter *formatter)
|
|
|
|
|
{
|
2015-02-24 22:39:14 +02:00
|
|
|
d->formatter = formatter;
|
2019-06-06 14:08:37 +02:00
|
|
|
if (d->formatter)
|
2017-03-01 16:26:34 +01:00
|
|
|
d->formatter->setPlainTextEdit(this);
|
2020-03-13 15:53:06 +01:00
|
|
|
else
|
|
|
|
|
d->formatter = &d->defaultFormatter;
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::showEvent(QShowEvent *e)
|
|
|
|
|
{
|
|
|
|
|
QPlainTextEdit::showEvent(e);
|
2015-02-24 22:39:14 +02:00
|
|
|
if (d->scrollToBottom)
|
2011-04-14 10:39:09 +02:00
|
|
|
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
2015-02-24 22:39:14 +02:00
|
|
|
d->scrollToBottom = false;
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
2015-08-17 16:29:09 +02:00
|
|
|
void OutputWindow::wheelEvent(QWheelEvent *e)
|
|
|
|
|
{
|
2019-05-24 14:52:19 +02:00
|
|
|
if (d->zoomEnabled) {
|
2015-08-17 16:29:09 +02:00
|
|
|
if (e->modifiers() & Qt::ControlModifier) {
|
|
|
|
|
float delta = e->angleDelta().y() / 120.f;
|
2019-07-15 19:59:30 +02:00
|
|
|
|
|
|
|
|
// Workaround for QTCREATORBUG-22721, remove when properly fixed in Qt
|
|
|
|
|
const float newSize = float(font().pointSizeF()) + delta;
|
|
|
|
|
if (delta < 0.f && newSize < 4.f)
|
|
|
|
|
return;
|
|
|
|
|
|
2015-08-17 16:29:09 +02:00
|
|
|
zoomInF(delta);
|
|
|
|
|
emit wheelZoom();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-08-31 21:58:07 +03:00
|
|
|
QAbstractScrollArea::wheelEvent(e);
|
|
|
|
|
updateMicroFocus();
|
2015-08-17 16:29:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setBaseFont(const QFont &newFont)
|
|
|
|
|
{
|
|
|
|
|
float zoom = fontZoom();
|
2019-05-24 14:52:19 +02:00
|
|
|
d->originalFontSize = newFont.pointSizeF();
|
2015-08-17 16:29:09 +02:00
|
|
|
QFont tmp = newFont;
|
2019-05-24 14:52:19 +02:00
|
|
|
float newZoom = qMax(d->originalFontSize + zoom, 4.0f);
|
2015-08-17 16:29:09 +02:00
|
|
|
tmp.setPointSizeF(newZoom);
|
|
|
|
|
setFont(tmp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
float OutputWindow::fontZoom() const
|
|
|
|
|
{
|
2019-05-24 14:52:19 +02:00
|
|
|
return font().pointSizeF() - d->originalFontSize;
|
2015-08-17 16:29:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setFontZoom(float zoom)
|
|
|
|
|
{
|
|
|
|
|
QFont f = font();
|
2019-05-24 14:52:19 +02:00
|
|
|
if (f.pointSizeF() == d->originalFontSize + zoom)
|
2015-08-17 16:29:09 +02:00
|
|
|
return;
|
2019-05-24 14:52:19 +02:00
|
|
|
float newZoom = qMax(d->originalFontSize + zoom, 4.0f);
|
2015-08-17 16:29:09 +02:00
|
|
|
f.setPointSizeF(newZoom);
|
|
|
|
|
setFont(f);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::setWheelZoomEnabled(bool enabled)
|
|
|
|
|
{
|
2019-05-24 14:52:19 +02:00
|
|
|
d->zoomEnabled = enabled;
|
2015-08-17 16:29:09 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-18 13:56:59 +01:00
|
|
|
void OutputWindow::updateFilterProperties(
|
|
|
|
|
const QString &filterText,
|
|
|
|
|
Qt::CaseSensitivity caseSensitivity,
|
|
|
|
|
bool isRegexp,
|
|
|
|
|
bool isInverted
|
|
|
|
|
)
|
2018-08-20 14:16:46 +02:00
|
|
|
{
|
2019-05-15 13:56:03 +02:00
|
|
|
FilterModeFlags flags;
|
|
|
|
|
flags.setFlag(FilterModeFlag::CaseSensitive, caseSensitivity == Qt::CaseSensitive)
|
2019-11-18 13:56:59 +01:00
|
|
|
.setFlag(FilterModeFlag::RegExp, isRegexp)
|
|
|
|
|
.setFlag(FilterModeFlag::Inverted, isInverted);
|
2019-05-15 13:56:03 +02:00
|
|
|
if (d->filterMode == flags && d->filterText == filterText)
|
|
|
|
|
return;
|
2019-06-21 12:25:07 +02:00
|
|
|
d->lastFilteredBlockNumber = -1;
|
2018-08-20 14:16:46 +02:00
|
|
|
if (d->filterText != filterText) {
|
2019-05-14 13:55:14 +02:00
|
|
|
const bool filterTextWasEmpty = d->filterText.isEmpty();
|
2018-08-20 14:16:46 +02:00
|
|
|
d->filterText = filterText;
|
|
|
|
|
|
|
|
|
|
// Update textedit's background color
|
2019-06-04 16:34:00 +02:00
|
|
|
if (filterText.isEmpty() && !filterTextWasEmpty) {
|
2019-05-14 14:33:08 +02:00
|
|
|
setPalette(d->originalPalette);
|
2019-05-24 14:52:19 +02:00
|
|
|
setReadOnly(d->originalReadOnly);
|
2019-06-04 16:34:00 +02:00
|
|
|
}
|
|
|
|
|
if (!filterText.isEmpty() && filterTextWasEmpty) {
|
|
|
|
|
d->originalReadOnly = isReadOnly();
|
2019-05-14 13:55:14 +02:00
|
|
|
setReadOnly(true);
|
2019-06-04 16:34:00 +02:00
|
|
|
const auto newBgColor = [this] {
|
|
|
|
|
const QColor currentColor = palette().color(QPalette::Base);
|
|
|
|
|
const int factor = 120;
|
|
|
|
|
return currentColor.value() < 128 ? currentColor.lighter(factor)
|
|
|
|
|
: currentColor.darker(factor);
|
|
|
|
|
};
|
|
|
|
|
QPalette p = palette();
|
|
|
|
|
p.setColor(QPalette::Base, newBgColor());
|
|
|
|
|
setPalette(p);
|
2019-05-14 13:55:14 +02:00
|
|
|
}
|
2018-08-20 14:16:46 +02:00
|
|
|
}
|
2019-05-15 13:56:03 +02:00
|
|
|
d->filterMode = flags;
|
|
|
|
|
filterNewContent();
|
2018-08-20 14:16:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputWindow::filterNewContent()
|
|
|
|
|
{
|
|
|
|
|
bool atBottom = isScrollbarAtBottom();
|
|
|
|
|
|
2019-06-21 12:25:07 +02:00
|
|
|
QTextBlock lastBlock = document()->findBlockByNumber(d->lastFilteredBlockNumber);
|
|
|
|
|
if (!lastBlock.isValid())
|
2019-05-14 14:36:00 +02:00
|
|
|
lastBlock = document()->begin();
|
2018-08-20 14:16:46 +02:00
|
|
|
|
2019-11-18 13:56:59 +01:00
|
|
|
const bool invert = d->filterMode.testFlag(FilterModeFlag::Inverted);
|
2018-08-20 14:16:46 +02:00
|
|
|
if (d->filterMode.testFlag(OutputWindow::FilterModeFlag::RegExp)) {
|
|
|
|
|
QRegularExpression regExp(d->filterText);
|
|
|
|
|
if (!d->filterMode.testFlag(OutputWindow::FilterModeFlag::CaseSensitive))
|
|
|
|
|
regExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
|
|
|
|
|
|
2019-05-14 14:36:00 +02:00
|
|
|
for (; lastBlock != document()->end(); lastBlock = lastBlock.next())
|
2018-08-20 14:16:46 +02:00
|
|
|
lastBlock.setVisible(d->filterText.isEmpty()
|
2019-11-18 13:56:59 +01:00
|
|
|
|| regExp.match(lastBlock.text()).hasMatch() != invert);
|
2018-08-20 14:16:46 +02:00
|
|
|
} else {
|
|
|
|
|
if (d->filterMode.testFlag(OutputWindow::FilterModeFlag::CaseSensitive)) {
|
2019-05-14 14:36:00 +02:00
|
|
|
for (; lastBlock != document()->end(); lastBlock = lastBlock.next())
|
2018-08-20 14:16:46 +02:00
|
|
|
lastBlock.setVisible(d->filterText.isEmpty()
|
2019-11-18 13:56:59 +01:00
|
|
|
|| lastBlock.text().contains(d->filterText) != invert);
|
2018-08-20 14:16:46 +02:00
|
|
|
} else {
|
2019-11-18 13:56:59 +01:00
|
|
|
for (; lastBlock != document()->end(); lastBlock = lastBlock.next()) {
|
|
|
|
|
lastBlock.setVisible(d->filterText.isEmpty() || lastBlock.text().toLower()
|
|
|
|
|
.contains(d->filterText.toLower()) != invert);
|
|
|
|
|
}
|
2018-08-20 14:16:46 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-21 12:25:07 +02:00
|
|
|
d->lastFilteredBlockNumber = document()->lastBlock().blockNumber();
|
2019-05-14 14:36:00 +02:00
|
|
|
|
|
|
|
|
// FIXME: Why on earth is this necessary? We should probably do something else instead...
|
|
|
|
|
setDocument(document());
|
2018-08-20 14:16:46 +02:00
|
|
|
|
|
|
|
|
if (atBottom)
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
|
2013-09-02 20:11:42 +02:00
|
|
|
QString OutputWindow::doNewlineEnforcement(const QString &out)
|
2011-04-14 10:39:09 +02:00
|
|
|
{
|
2015-02-24 22:39:14 +02:00
|
|
|
d->scrollToBottom = true;
|
2011-04-14 10:39:09 +02:00
|
|
|
QString s = out;
|
2015-02-24 22:39:14 +02:00
|
|
|
if (d->enforceNewline) {
|
2020-02-17 14:46:04 +01:00
|
|
|
s.prepend('\n');
|
2015-02-24 22:39:14 +02:00
|
|
|
d->enforceNewline = false;
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-17 14:46:04 +01:00
|
|
|
if (s.endsWith('\n')) {
|
2015-02-24 22:39:14 +02:00
|
|
|
d->enforceNewline = true; // make appendOutputInline put in a newline next time
|
2011-04-14 10:39:09 +02:00
|
|
|
s.chop(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-25 13:58:24 +02:00
|
|
|
void OutputWindow::setMaxCharCount(int count)
|
2011-04-29 09:36:04 +02:00
|
|
|
{
|
2018-09-25 13:58:24 +02:00
|
|
|
d->maxCharCount = count;
|
|
|
|
|
setMaximumBlockCount(count / 100);
|
2015-02-24 22:39:14 +02:00
|
|
|
}
|
|
|
|
|
|
2018-09-25 13:58:24 +02:00
|
|
|
int OutputWindow::maxCharCount() const
|
2015-02-24 22:39:14 +02:00
|
|
|
{
|
2018-09-25 13:58:24 +02:00
|
|
|
return d->maxCharCount;
|
2011-04-29 09:36:04 +02:00
|
|
|
}
|
|
|
|
|
|
2011-04-14 10:39:09 +02:00
|
|
|
void OutputWindow::appendMessage(const QString &output, OutputFormat format)
|
|
|
|
|
{
|
2019-12-03 16:28:56 +01:00
|
|
|
QString out = output;
|
|
|
|
|
if (d->prependCarriageReturn) {
|
|
|
|
|
d->prependCarriageReturn = false;
|
|
|
|
|
out.prepend('\r');
|
|
|
|
|
}
|
|
|
|
|
out = SynchronousProcess::normalizeNewlines(out);
|
|
|
|
|
if (out.endsWith('\r')) {
|
|
|
|
|
d->prependCarriageReturn = true;
|
|
|
|
|
out.chop(1);
|
|
|
|
|
}
|
2018-09-25 10:50:32 +02:00
|
|
|
|
|
|
|
|
if (out.size() > d->maxCharCount) {
|
|
|
|
|
// Current line alone exceeds limit, we need to cut it.
|
|
|
|
|
out.truncate(d->maxCharCount);
|
|
|
|
|
out.append("[...]");
|
|
|
|
|
setMaximumBlockCount(1);
|
|
|
|
|
} else {
|
|
|
|
|
int plannedChars = document()->characterCount() + out.size();
|
|
|
|
|
if (plannedChars > d->maxCharCount) {
|
|
|
|
|
int plannedBlockCount = document()->blockCount();
|
|
|
|
|
QTextBlock tb = document()->firstBlock();
|
|
|
|
|
while (tb.isValid() && plannedChars > d->maxCharCount && plannedBlockCount > 1) {
|
|
|
|
|
plannedChars -= tb.length();
|
|
|
|
|
plannedBlockCount -= 1;
|
|
|
|
|
tb = tb.next();
|
|
|
|
|
}
|
|
|
|
|
setMaximumBlockCount(plannedBlockCount);
|
|
|
|
|
} else {
|
|
|
|
|
setMaximumBlockCount(-1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 17:24:52 +02:00
|
|
|
const bool atBottom = isScrollbarAtBottom() || m_scrollTimer.isActive();
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
if (format == ErrorMessageFormat || format == NormalMessageFormat) {
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->appendMessage(doNewlineEnforcement(out), format);
|
2011-04-14 10:39:09 +02:00
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
bool sameLine = format == StdOutFormatSameLine
|
|
|
|
|
|| format == StdErrFormatSameLine;
|
|
|
|
|
|
|
|
|
|
if (sameLine) {
|
2015-02-24 22:39:14 +02:00
|
|
|
d->scrollToBottom = true;
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2015-02-24 22:39:14 +02:00
|
|
|
bool enforceNewline = d->enforceNewline;
|
|
|
|
|
d->enforceNewline = false;
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2018-11-16 00:18:13 +02:00
|
|
|
if (enforceNewline) {
|
|
|
|
|
out.prepend('\n');
|
|
|
|
|
} else {
|
2020-02-17 14:46:04 +01:00
|
|
|
const int newline = out.indexOf('\n');
|
2011-04-14 10:39:09 +02:00
|
|
|
moveCursor(QTextCursor::End);
|
2019-12-02 17:32:37 +01:00
|
|
|
if (newline != -1) {
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->appendMessage(out.left(newline), format);// doesn't enforce new paragraph like appendPlainText
|
2019-12-02 17:32:37 +01:00
|
|
|
out = out.mid(newline);
|
|
|
|
|
}
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-02 17:32:37 +01:00
|
|
|
if (out.isEmpty()) {
|
2015-02-24 22:39:14 +02:00
|
|
|
d->enforceNewline = true;
|
2011-04-14 10:39:09 +02:00
|
|
|
} else {
|
2020-02-17 14:46:04 +01:00
|
|
|
if (out.endsWith('\n')) {
|
2015-02-24 22:39:14 +02:00
|
|
|
d->enforceNewline = true;
|
2019-12-02 17:32:37 +01:00
|
|
|
out.chop(1);
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->appendMessage(out, format);
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->appendMessage(doNewlineEnforcement(out), format);
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-28 17:24:52 +02:00
|
|
|
if (atBottom) {
|
|
|
|
|
if (m_lastMessage.elapsed() < 5) {
|
|
|
|
|
m_scrollTimer.start();
|
|
|
|
|
} else {
|
|
|
|
|
m_scrollTimer.stop();
|
|
|
|
|
scrollToBottom();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_lastMessage.start();
|
2011-04-14 10:39:09 +02:00
|
|
|
enableUndoRedo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool OutputWindow::isScrollbarAtBottom() const
|
|
|
|
|
{
|
|
|
|
|
return verticalScrollBar()->value() == verticalScrollBar()->maximum();
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-08 14:33:02 +01:00
|
|
|
QMimeData *OutputWindow::createMimeDataFromSelection() const
|
|
|
|
|
{
|
|
|
|
|
const auto mimeData = new QMimeData;
|
|
|
|
|
QString content;
|
|
|
|
|
const int selStart = textCursor().selectionStart();
|
|
|
|
|
const int selEnd = textCursor().selectionEnd();
|
|
|
|
|
const QTextBlock firstBlock = document()->findBlock(selStart);
|
|
|
|
|
const QTextBlock lastBlock = document()->findBlock(selEnd);
|
|
|
|
|
for (QTextBlock curBlock = firstBlock; curBlock != lastBlock; curBlock = curBlock.next()) {
|
|
|
|
|
if (!curBlock.isVisible())
|
|
|
|
|
continue;
|
|
|
|
|
if (curBlock == firstBlock)
|
|
|
|
|
content += curBlock.text().mid(selStart - firstBlock.position());
|
|
|
|
|
else
|
|
|
|
|
content += curBlock.text();
|
|
|
|
|
content += '\n';
|
|
|
|
|
}
|
|
|
|
|
if (lastBlock.isValid() && lastBlock.isVisible()) {
|
|
|
|
|
if (firstBlock == lastBlock)
|
|
|
|
|
content = textCursor().selectedText();
|
|
|
|
|
else
|
|
|
|
|
content += lastBlock.text().mid(0, selEnd - lastBlock.position());
|
|
|
|
|
}
|
|
|
|
|
mimeData->setText(content);
|
|
|
|
|
return mimeData;
|
|
|
|
|
}
|
|
|
|
|
|
2011-04-14 10:39:09 +02:00
|
|
|
void OutputWindow::clear()
|
|
|
|
|
{
|
2015-02-24 22:39:14 +02:00
|
|
|
d->enforceNewline = false;
|
2019-12-03 16:28:56 +01:00
|
|
|
d->prependCarriageReturn = false;
|
2011-04-14 10:39:09 +02:00
|
|
|
QPlainTextEdit::clear();
|
2020-03-13 15:53:06 +01:00
|
|
|
d->formatter->clear();
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
{
|
2015-02-24 22:45:44 +02:00
|
|
|
if (!d->cursor.atEnd())
|
|
|
|
|
d->cursor.movePosition(QTextCursor::End);
|
|
|
|
|
QTextCharFormat endFormat = d->cursor.charFormat();
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2015-02-24 22:45:44 +02:00
|
|
|
d->cursor.select(QTextCursor::Document);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
|
|
|
|
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()) ));
|
2015-02-24 22:45:44 +02:00
|
|
|
d->cursor.mergeCharFormat(format);
|
2011-04-14 10:39:09 +02:00
|
|
|
|
2015-05-07 12:59:01 +02:00
|
|
|
d->cursor.movePosition(QTextCursor::End);
|
2015-02-24 22:45:44 +02:00
|
|
|
d->cursor.setCharFormat(endFormat);
|
|
|
|
|
d->cursor.insertBlock(QTextBlockFormat());
|
2011-04-14 10:39:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|