2026-04-06 00:20:51 -05:00

845 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <QMutex>
#include <QMutexLocker>
#include <QTimer>
#include <QTextBlock>
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QPainter>
#include <Utils/CustomElements.h>
#include <Utils/NonBlockingDialogs.h>
VerticalTabBar::VerticalTabBar(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true);
setFixedWidth(m_tabWidth);
}
int VerticalTabBar::addTab(const QString &text)
{
m_tabs.append({text});
if (m_currentIndex < 0)
setCurrentIndex(0);
update();
return m_tabs.size() - 1;
}
void VerticalTabBar::removeTab(int index)
{
if (index < 0 || index >= m_tabs.size()) return;
m_tabs.removeAt(index);
if (m_currentIndex >= m_tabs.size())
setCurrentIndex(m_tabs.size() - 1);
update();
}
void VerticalTabBar::setCurrentIndex(int index)
{
if (index == m_currentIndex || index < 0 || index >= m_tabs.size()) return;
m_currentIndex = index;
Q_EMIT currentChanged(index);
update();
}
QString VerticalTabBar::tabText(int index) const
{
if (index < 0 || index >= m_tabs.size()) return QString();
return m_tabs[index].text;
}
int VerticalTabBar::tabAt(const QPoint &pos) const
{
int y = pos.y();
int offset = m_showAddButton ? m_tabHeight : 0;
if (y < offset) return -1;
int index = (y - offset) / m_tabHeight;
if (index >= 0 && index < m_tabs.size())
return index;
return -1;
}
QRect VerticalTabBar::closeButtonRect(int index) const
{
int offset = m_showAddButton ? m_tabHeight : 0;
int y = offset + index * m_tabHeight;
return QRect(m_tabWidth - 14, y + 4, 12, 12);
}
QRect VerticalTabBar::addButtonRect() const
{
return QRect(0, 0, m_tabWidth, m_tabHeight);
}
void VerticalTabBar::paintEvent(QPaintEvent *)
{
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
const QPalette &pal = palette();
QColor bgNormal = pal.color(QPalette::Window);
QColor bgHover = pal.color(QPalette::Mid);
QColor bgSelected = pal.color(QPalette::Highlight);
QColor textNormal = pal.color(QPalette::WindowText);
QColor textSelected = pal.color(QPalette::HighlightedText);
QColor borderColor = pal.color(QPalette::Dark);
int yOffset = 0;
if (m_showAddButton) {
QRect addRect = addButtonRect();
if (m_addButtonHovered) {
p.fillRect(addRect, bgHover);
}
p.setPen(textNormal);
QFont f = font();
f.setBold(true);
f.setPointSize(f.pointSize() + 2);
p.setFont(f);
p.drawText(addRect, Qt::AlignCenter, "+");
p.setPen(borderColor);
p.drawLine(addRect.bottomLeft(), addRect.bottomRight());
yOffset = m_tabHeight;
}
for (int i = 0; i < m_tabs.size(); ++i) {
QRect tabRect(0, yOffset + i * m_tabHeight, m_tabWidth, m_tabHeight);
bool selected = (i == m_currentIndex);
bool hovered = (i == m_hoveredIndex);
if (selected) {
p.fillRect(tabRect, bgSelected);
} else if (hovered) {
p.fillRect(tabRect, bgHover);
}
if (selected) {
p.fillRect(0, tabRect.top(), 3, tabRect.height(), pal.color(QPalette::Highlight));
}
p.setPen(selected ? textSelected : textNormal);
QFont f = font();
f.setBold(selected);
p.setFont(f);
QString num = QString::number(i + 1);
p.drawText(tabRect, Qt::AlignCenter, num);
if (m_closable && (selected || hovered)) {
QRect closeRect = closeButtonRect(i);
if (m_hoveredCloseButton == i) {
p.setBrush(QColor(200, 60, 60));
p.setPen(Qt::NoPen);
p.drawEllipse(closeRect);
p.setPen(Qt::white);
} else {
p.setPen(selected ? textSelected : textNormal);
}
p.drawText(closeRect, Qt::AlignCenter, "×");
}
if (i < m_tabs.size() - 1) {
p.setPen(borderColor);
p.drawLine(tabRect.bottomLeft(), tabRect.bottomRight());
}
}
}
void VerticalTabBar::mousePressEvent(QMouseEvent *event)
{
if (m_showAddButton && addButtonRect().contains(event->pos())) {
Q_EMIT addTabRequested();
return;
}
int index = tabAt(event->pos());
if (index >= 0) {
if (m_closable && closeButtonRect(index).contains(event->pos())) {
Q_EMIT tabCloseRequested(index);
} else {
setCurrentIndex(index);
}
}
}
void VerticalTabBar::mouseMoveEvent(QMouseEvent *event)
{
int oldHover = m_hoveredIndex;
int oldCloseHover = m_hoveredCloseButton;
bool oldAddHover = m_addButtonHovered;
m_addButtonHovered = m_showAddButton && addButtonRect().contains(event->pos());
m_hoveredIndex = tabAt(event->pos());
m_hoveredCloseButton = (m_closable && m_hoveredIndex >= 0 && closeButtonRect(m_hoveredIndex).contains(event->pos())) ? m_hoveredIndex : -1;
if (oldHover != m_hoveredIndex || oldCloseHover != m_hoveredCloseButton || oldAddHover != m_addButtonHovered)
update();
}
void VerticalTabBar::leaveEvent(QEvent *)
{
m_hoveredIndex = -1;
m_hoveredCloseButton = -1;
m_addButtonHovered = false;
update();
}
QSize VerticalTabBar::sizeHint() const
{
int addButtonHeight = m_showAddButton ? m_tabHeight : 0;
return QSize(m_tabWidth, addButtonHeight + m_tabs.size() * m_tabHeight);
}
VerticalTabWidget::VerticalTabWidget(QWidget *parent) : QWidget(parent)
{
m_tabBar = new VerticalTabBar(this);
m_stack = new QStackedWidget(this);
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_tabBar);
layout->addWidget(m_stack, 1);
connect(m_tabBar, &VerticalTabBar::currentChanged, this, [this](int index) {
m_stack->setCurrentIndex(index);
Q_EMIT currentChanged(index);
});
connect(m_tabBar, &VerticalTabBar::tabCloseRequested, this, &VerticalTabWidget::tabCloseRequested);
}
int VerticalTabWidget::addTab(QWidget *widget, const QString &label)
{
int index = m_tabBar->addTab(label);
m_stack->insertWidget(index, widget);
return index;
}
void VerticalTabWidget::removeTab(int index)
{
QWidget *w = m_stack->widget(index);
m_stack->removeWidget(w);
m_tabBar->removeTab(index);
}
void VerticalTabWidget::setCurrentIndex(int index)
{
m_tabBar->setCurrentIndex(index);
}
QWidget *VerticalTabWidget::widget(int index) const
{
return m_stack->widget(index);
}
void VerticalTabWidget::setCornerWidget(QWidget *widget)
{
if (m_cornerWidget) {
layout()->removeWidget(m_cornerWidget);
}
m_cornerWidget = widget;
if (widget) {
static_cast<QHBoxLayout*>(layout())->insertWidget(0, widget);
}
}
BoldHeaderView::BoldHeaderView(Qt::Orientation orientation, QWidget *parent)
: QHeaderView(orientation, parent)
{
setDefaultAlignment(Qt::AlignCenter);
setMinimumSectionSize(24);
setDefaultSectionSize(100);
setSectionsClickable(true);
setSortIndicatorShown(true);
}
void BoldHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
if (!rect.isValid())
return;
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
const QPalette &pal = palette();
QColor bgColor = pal.color(QPalette::Window).darker(115);
QColor textColor = pal.color(QPalette::ButtonText);
painter->fillRect(rect, bgColor);
QString text = model()->headerData(logicalIndex, orientation(), Qt::DisplayRole).toString();
QFont boldFont = painter->font();
boldFont.setBold(true);
painter->setFont(boldFont);
painter->setPen(textColor);
bool hasSortIndicator = isSortIndicatorShown() && sortIndicatorSection() == logicalIndex;
QRect textRect = hasSortIndicator ? rect.adjusted(4, 0, -16, 0) : rect;
painter->drawText(textRect, Qt::AlignCenter, text);
if (hasSortIndicator) {
int arrowSize = 6;
int centerY = rect.center().y();
int arrowX = rect.right() - 10;
painter->setPen(Qt::NoPen);
painter->setBrush(textColor); // Same color as text
QPolygon triangle;
if (sortIndicatorOrder() == Qt::AscendingOrder) {
triangle << QPoint(arrowX, centerY + arrowSize/2)
<< QPoint(arrowX + arrowSize, centerY + arrowSize/2)
<< QPoint(arrowX + arrowSize/2, centerY - arrowSize/2);
} else {
triangle << QPoint(arrowX, centerY - arrowSize/2)
<< QPoint(arrowX + arrowSize, centerY - arrowSize/2)
<< QPoint(arrowX + arrowSize/2, centerY + arrowSize/2);
}
painter->drawPolygon(triangle);
}
painter->restore();
}
CardListWidget::CardListWidget(QWidget *parent) : QListWidget(parent)
{
setMouseTracking(true);
setSpacing(1);
setItemDelegate(new CardListDelegate(this));
updateColorsFromPalette();
}
void CardListWidget::updateColorsFromPalette()
{
const QPalette &pal = palette();
m_itemBackground = pal.color(QPalette::AlternateBase);
m_itemBackgroundHover = pal.color(QPalette::Mid);
m_itemBackgroundSelected = pal.color(QPalette::Highlight);
m_titleColor = pal.color(QPalette::Text);
m_titleColorSelected = pal.color(QPalette::HighlightedText);
m_subtitleColor = pal.color(QPalette::Text);
m_subtitleColor.setAlpha(160);
m_subtitleColorSelected = pal.color(QPalette::HighlightedText);
m_subtitleColorSelected.setAlpha(200);
}
void CardListWidget::addCard(const QString &title, const QString &text)
{
auto* item = new QListWidgetItem(this);
item->setData(TitleRole, title);
item->setData(TextRole, text);
}
CardListDelegate::CardListDelegate(QObject *parent) : QStyledItemDelegate(parent) {}
void CardListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setRenderHint(QPainter::TextAntialiasing, true);
QString title = index.data(CardListWidget::TitleRole).toString();
QString text = index.data(CardListWidget::TextRole).toString();
const bool isSelected = option.state & QStyle::State_Selected;
const bool isHovered = option.state & QStyle::State_MouseOver;
auto* cardList = qobject_cast<const CardListWidget*>(option.widget);
constexpr int cardMargin = 3;
QRect cardRect = option.rect.adjusted(cardMargin, 2, -cardMargin, -2);
QColor bgColor, titleColor, subtitleColor;
if (cardList) {
if (isSelected) {
bgColor = cardList->itemBackgroundSelected();
titleColor = cardList->titleColorSelected();
subtitleColor = cardList->subtitleColorSelected();
} else if (isHovered) {
bgColor = cardList->itemBackgroundHover();
titleColor = cardList->titleColor();
subtitleColor = cardList->subtitleColor();
} else {
bgColor = cardList->itemBackground();
titleColor = cardList->titleColor();
subtitleColor = cardList->subtitleColor();
}
} else {
bgColor = isSelected ? option.palette.highlight().color() : option.palette.mid().color();
if (!isSelected) bgColor.setAlpha(40);
titleColor = isSelected ? option.palette.highlightedText().color() : option.palette.text().color();
subtitleColor = titleColor;
subtitleColor.setAlpha(isSelected ? 200 : 140);
}
painter->fillRect(cardRect, bgColor);
constexpr int hPadding = 12;
constexpr int vPadding = 10;
constexpr int lineSpacing = 4;
QRect contentRect = cardRect.adjusted(hPadding, vPadding, -hPadding, -vPadding);
QFont titleFont = option.font;
titleFont.setWeight(QFont::Bold);
QFontMetrics titleFm(titleFont);
painter->setFont(titleFont);
painter->setPen(titleColor);
QRect titleRect = contentRect;
titleRect.setHeight(titleFm.height());
QString elidedTitle = titleFm.elidedText(title, Qt::ElideRight, titleRect.width());
painter->drawText(titleRect, Qt::AlignLeft | Qt::AlignVCenter, elidedTitle);
if (!text.isEmpty()) {
QFont subtitleFont = option.font;
int subtitleSize = qMax(subtitleFont.pointSize() - 1, 8);
subtitleFont.setPointSize(subtitleSize);
QFontMetrics subtitleFm(subtitleFont);
painter->setFont(subtitleFont);
painter->setPen(subtitleColor);
QRect subtitleRect = contentRect;
subtitleRect.setTop(titleRect.bottom() + lineSpacing);
subtitleRect.setHeight(subtitleFm.height());
QString elidedSubtitle = subtitleFm.elidedText(text, Qt::ElideRight, subtitleRect.width());
painter->drawText(subtitleRect, Qt::AlignLeft | Qt::AlignVCenter, elidedSubtitle);
}
painter->restore();
}
QSize CardListDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
Q_UNUSED(index)
constexpr int cardMargin = 3;
constexpr int vPadding = 10;
constexpr int lineSpacing = 4;
QFont titleFont = option.font;
titleFont.setWeight(QFont::Medium);
QFontMetrics titleFm(titleFont);
QFont subtitleFont = option.font;
int subtitleSize = qMax(subtitleFont.pointSize() - 1, 8);
subtitleFont.setPointSize(subtitleSize);
QFontMetrics subtitleFm(subtitleFont);
int totalHeight = cardMargin + vPadding + titleFm.height() + lineSpacing + subtitleFm.height() + vPadding + cardMargin;
return QSize(190, totalHeight);
}
SpinTable::SpinTable(int rows, int columns, QWidget* parent)
{
this->setParent(parent);
buttonAdd = new QPushButton("Add");
buttonClear = new QPushButton("Clear");
tableModel = new QStandardItemModel(rows, columns, this);
table = new QTableView(this);
table->setModel(tableModel);
table->setAutoFillBackground( false );
table->setShowGrid( false );
table->setSortingEnabled( true );
table->setWordWrap( true );
table->setCornerButtonEnabled( false );
table->setSelectionBehavior( QAbstractItemView::SelectRows );
table->setSelectionMode( QAbstractItemView::SingleSelection );
table->setFocusPolicy( Qt::NoFocus );
table->setAlternatingRowColors( true );
table->horizontalHeader()->setSectionResizeMode( QHeaderView::Stretch );
table->horizontalHeader()->setCascadingSectionResizes( true );
table->horizontalHeader()->setHighlightSections( false );
table->verticalHeader()->setVisible( false );
layout = new QGridLayout( this );
layout->addWidget(table, 0, 0, 1, 2);
layout->addWidget(buttonAdd, 1, 0, 1, 1);
layout->addWidget(buttonClear, 1, 1, 1, 1);
this->setLayout(layout);
QObject::connect(buttonAdd, &QPushButton::clicked, this, [&]()
{
if (tableModel->rowCount() < 1 )
tableModel->setRowCount(1 );
else
tableModel->setRowCount(tableModel->rowCount() + 1 );
tableModel->setItem(tableModel->rowCount() - 1, 0, new QStandardItem() );
table->selectRow(tableModel->rowCount() - 1 );
} );
QObject::connect(buttonClear, &QPushButton::clicked, this, [&](){ tableModel->setRowCount(0); } );
}
ConsoleHighlighter::ConsoleHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) {}
void ConsoleHighlighter::highlightBlock(const QString& text)
{
Q_UNUSED(text)
auto* data = dynamic_cast<ConsoleBlockData*>(currentBlockUserData());
if (!data)
return;
for (const auto& range : data->formats)
setFormat(range.start, range.length, range.format);
}
TextEditConsole::TextEditConsole(QWidget* parent, int maxLines, bool noWrap, bool autoScroll)
: QPlainTextEdit(parent), cachedCursor(this->textCursor()), maxLines(maxLines), autoScroll(autoScroll), noWrap(noWrap)
{
cachedCursor.movePosition(QTextCursor::End);
if (noWrap)
setLineWrapMode(QPlainTextEdit::NoWrap);
else
setLineWrapMode(QPlainTextEdit::WidgetWidth);
highlighter = new ConsoleHighlighter(this->document());
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &TextEditConsole::customContextMenuRequested, this, &TextEditConsole::createContextMenu);
batchMutex = new QMutex();
batchTimer = new QTimer(this);
batchTimer->setSingleShot(true);
batchTimer->setInterval(BATCH_INTERVAL_MS);
connect(batchTimer, &QTimer::timeout, this, &TextEditConsole::flushPending);
}
void TextEditConsole::createContextMenu(const QPoint &pos) {
QMenu *menu = new QMenu(this);
QAction *copyAction = menu->addAction("Copy (Ctrl + C)");
connect(copyAction, &QAction::triggered, this, [this]() { copy(); });
QAction *selectAllAction = menu->addAction("Select All (Ctrl + A)");
connect(selectAllAction, &QAction::triggered, this, [this]() { selectAll(); });
QAction *findAction = menu->addAction("Find (Ctrl + F)");
connect(findAction, &QAction::triggered, this, [this]() { Q_EMIT ctx_find(); });
QAction *clearAction = menu->addAction("Clear (Ctrl + L)");
connect(clearAction, &QAction::triggered, this, [this]() { clear(); });
menu->addSeparator();
QAction *showHistory = menu->addAction("Show history (Ctrl + H)");
connect(showHistory, &QAction::triggered, this, [this]() { Q_EMIT ctx_history(); });
QAction *setBufferSizeAction = menu->addAction("Set buffer size...");
connect(setBufferSizeAction, &QAction::triggered, this, [this]() {
bool ok;
int newSize = QInputDialog::getInt(this, "Set buffer size", "Enter maximum number of lines:", maxLines, 100, 100000, 100, &ok);
if (ok)
setBufferSize(newSize);
});
QAction *noWrapAction = menu->addAction("No Wrap");
noWrapAction->setCheckable(true);
noWrapAction->setChecked(noWrap);
connect(noWrapAction, &QAction::toggled, this, [this](bool checked) {
noWrap = checked;
if (checked)
setLineWrapMode(QPlainTextEdit::NoWrap);
else
setLineWrapMode(QPlainTextEdit::WidgetWidth);
});
QAction *autoScrollAction = menu->addAction("Auto scroll");
autoScrollAction->setCheckable(true);
autoScrollAction->setChecked(autoScroll);
connect(autoScrollAction, &QAction::toggled, this, &TextEditConsole::setAutoScrollEnabled);
QAction *bgImageAction = menu->addAction("Show background image");
bgImageAction->setCheckable(true);
bgImageAction->setChecked(showBgImage);
connect(bgImageAction, &QAction::toggled, this, [this](bool checked) {
showBgImage = checked;
Q_EMIT ctx_bgToggled(checked);
});
menu->exec(mapToGlobal(pos));
delete menu;
}
void TextEditConsole::setMaxLines(const int lines)
{
maxLines = lines;
trimExcessLines();
}
void TextEditConsole::setBufferSize(const int size) {
if (size > 0) {
maxLines = size;
trimExcessLines();
}
}
void TextEditConsole::setAutoScrollEnabled(const bool enabled) {
autoScroll = enabled;
if (enabled)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
bool TextEditConsole::isAutoScrollEnabled() const {
return autoScroll;
}
bool TextEditConsole::isShowBackgroundImage() const {
return showBgImage;
}
void TextEditConsole::setShowBackgroundImage(const bool enabled) {
showBgImage = enabled;
Q_EMIT ctx_bgToggled(enabled);
}
void TextEditConsole::setConsoleBackground(const QColor& bgColor, const QString& imagePath, int dimming)
{
m_bgColor = bgColor;
m_bgDimming = qBound(0, dimming, 100);
if (!imagePath.isEmpty()) {
m_bgPixmap = QPixmap(imagePath);
m_hasBgImage = !m_bgPixmap.isNull();
} else {
m_bgPixmap = QPixmap();
m_hasBgImage = false;
}
updatePaletteBackground();
viewport()->update();
}
void TextEditConsole::updatePaletteBackground()
{
QPalette p = palette();
if (showBgImage && m_hasBgImage)
p.setColor(QPalette::Base, Qt::transparent);
else
p.setColor(QPalette::Base, m_bgColor);
setPalette(p);
}
void TextEditConsole::paintEvent(QPaintEvent* event)
{
if (showBgImage && m_hasBgImage) {
QPainter painter(viewport());
QRect rect = viewport()->rect();
painter.fillRect(rect, m_bgColor);
QPixmap scaled = m_bgPixmap.scaled(rect.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation);
int x = (rect.width() - scaled.width()) / 2;
int y = (rect.height() - scaled.height()) / 2;
painter.drawPixmap(x, y, scaled);
QColor overlay = m_bgColor;
overlay.setAlpha(qRound(m_bgDimming * 255.0 / 100.0));
painter.fillRect(rect, overlay);
}
QPlainTextEdit::paintEvent(event);
}
bool TextEditConsole::isNoWrapEnabled() const {
return noWrap;
}
void TextEditConsole::appendChunk(const QString& text, const QTextCharFormat& fmt)
{
QMutexLocker locker(batchMutex);
if (!pendingChunks.isEmpty() && pendingChunks.last().format == fmt) {
pendingChunks.last().text += text;
} else {
pendingChunks.append({text, fmt});
}
pendingSize += text.size();
if (syncMode)
return;
if (pendingSize >= MAX_BATCH_SIZE) {
locker.unlock();
flushPending();
} else if (!batchTimer->isActive()) {
batchTimer->start();
}
}
void TextEditConsole::insertChunks(const QList<FormattedChunk>& chunks)
{
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum() - 4;
highlighter->blockSignals(true);
cachedCursor.movePosition(QTextCursor::End);
QTextBlock lastBlock = document()->lastBlock();
for (const auto& chunk : chunks) {
if (chunk.text.isEmpty())
continue;
bool hasFormat = chunk.format != QTextCharFormat();
QString normalizedText = chunk.text;
normalizedText.replace("\r\n", "\n");
normalizedText.remove('\r');
QStringList lines = normalizedText.split('\n');
for (int i = 0; i < lines.size(); ++i) {
const QString& line = lines[i];
if (!line.isEmpty()) {
QTextBlock block = cachedCursor.block();
int posInBlock = cachedCursor.positionInBlock();
cachedCursor.insertText(line);
if (hasFormat) {
auto* data = dynamic_cast<ConsoleBlockData*>(block.userData());
if (!data) {
data = new ConsoleBlockData();
block.setUserData(data);
}
data->formats.append({posInBlock, static_cast<int>(line.length()), chunk.format});
}
}
if (i < lines.size() - 1) {
cachedCursor.insertBlock();
}
}
}
highlighter->blockSignals(false);
highlighter->rehighlight();
appendCount++;
auto doc = this->document();
int currentLines = doc->blockCount();
int trimThreshold = static_cast<int>(maxLines * 1.5);
if (currentLines > trimThreshold) {
trimExcessLines();
appendCount = 0;
} else if (appendCount >= 200 && currentLines > static_cast<int>(maxLines * 0.9)) {
trimExcessLines();
appendCount = 0;
}
if (autoScroll || atBottom)
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
}
void TextEditConsole::appendPlain(const QString& text)
{
appendChunk(text, QTextCharFormat());
}
void TextEditConsole::flushPending()
{
QMutexLocker locker(batchMutex);
if (pendingChunks.isEmpty())
return;
QList<FormattedChunk> chunks = std::move(pendingChunks);
pendingChunks.clear();
pendingSize = 0;
locker.unlock();
insertChunks(chunks);
}
void TextEditConsole::appendFormatted(const QString& text, const std::function<void(QTextCharFormat&)> &styleFn)
{
QTextCharFormat fmt;
styleFn(fmt);
appendChunk(text, fmt);
}
void TextEditConsole::setSyncMode(bool enabled)
{
syncMode = enabled;
if (!enabled)
flushAll();
}
void TextEditConsole::flushAll()
{
QMutexLocker locker(batchMutex);
if (pendingChunks.isEmpty()) {
pendingSize = 0;
return;
}
QList<FormattedChunk> chunks = std::move(pendingChunks);
pendingChunks.clear();
pendingSize = 0;
locker.unlock();
insertChunks(chunks);
}
void TextEditConsole::appendColor(const QString& text, const QColor color) {
appendFormatted(text, [=](QTextCharFormat& fmt) { fmt.setForeground(color); });
}
void TextEditConsole::appendBold(const QString& text) {
appendFormatted(text, [](QTextCharFormat& fmt) { fmt.setFontWeight(QFont::Bold); });
}
void TextEditConsole::appendUnderline(const QString& text) {
appendFormatted(text, [](QTextCharFormat& fmt) { fmt.setFontUnderline(true); });
}
void TextEditConsole::appendColorBold(const QString& text, const QColor color) {
appendFormatted(text, [=](QTextCharFormat& fmt) {
fmt.setForeground(color);
fmt.setFontWeight(QFont::Bold);
});
}
void TextEditConsole::appendColorUnderline(const QString &text, const QColor color) {
appendFormatted(text, [=](QTextCharFormat& fmt) {
fmt.setForeground(color);
fmt.setFontUnderline(true);
});
}
void TextEditConsole::trimExcessLines() {
auto doc = this->document();
int blockCount = doc->blockCount();
if (blockCount <= maxLines)
return;
QTextCursor c(doc);
c.movePosition(QTextCursor::Start);
QTextBlock keepFromBlock = doc->findBlockByNumber(blockCount - maxLines);
if (keepFromBlock.isValid()) {
c.movePosition(QTextCursor::Start);
c.setPosition(keepFromBlock.position(), QTextCursor::KeepAnchor);
c.removeSelectedText();
}
}