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

527 lines
18 KiB
C++

#ifndef ADAPTIXCLIENT_CUSTOMELEMENTS_H
#define ADAPTIXCLIENT_CUSTOMELEMENTS_H
#include <main.h>
#include <QTextLayout>
#include <QToolTip>
#include <QHeaderView>
#include <QStackedWidget>
#include <QSyntaxHighlighter>
#include <QPlainTextEdit>
class QTimer;
class QMutex;
class VerticalTabBar : public QWidget
{
Q_OBJECT
struct TabInfo { QString text; };
QVector<TabInfo> m_tabs;
int m_currentIndex = -1;
int m_hoveredIndex = -1;
int m_hoveredCloseButton = -1;
bool m_closable = false;
int m_tabHeight = 28;
int m_tabWidth = 32;
bool m_showAddButton = false;
bool m_addButtonHovered = false;
int tabAt(const QPoint &pos) const;
QRect closeButtonRect(int index) const;
QRect addButtonRect() const;
public:
explicit VerticalTabBar(QWidget *parent = nullptr);
int addTab(const QString &text);
void removeTab(int index);
int count() const { return m_tabs.size(); }
int currentIndex() const { return m_currentIndex; }
void setCurrentIndex(int index);
void setTabsClosable(bool closable) { m_closable = closable; update(); }
void setShowAddButton(bool show) { m_showAddButton = show; update(); }
QString tabText(int index) const;
Q_SIGNALS:
void currentChanged(int index);
void tabCloseRequested(int index);
void addTabRequested();
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
QSize sizeHint() const override;
};
class VerticalTabWidget : public QWidget
{
Q_OBJECT
public:
explicit VerticalTabWidget(QWidget *parent = nullptr);
int addTab(QWidget *widget, const QString &label);
void removeTab(int index);
int count() const { return m_tabBar->count(); }
int currentIndex() const { return m_tabBar->currentIndex(); }
void setCurrentIndex(int index);
QWidget *widget(int index) const;
void setTabsClosable(bool closable) { m_tabBar->setTabsClosable(closable); }
VerticalTabBar *tabBar() const { return m_tabBar; }
void setCornerWidget(QWidget *widget);
Q_SIGNALS:
void currentChanged(int index);
void tabCloseRequested(int index);
private:
VerticalTabBar *m_tabBar;
QStackedWidget *m_stack;
QWidget *m_cornerWidget = nullptr;
};
class BoldHeaderView : public QHeaderView
{
Q_OBJECT
public:
explicit BoldHeaderView(Qt::Orientation orientation, QWidget *parent = nullptr);
protected:
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
};
class CardListWidget : public QListWidget
{
Q_OBJECT
Q_PROPERTY(QColor itemBackground READ itemBackground WRITE setItemBackground)
Q_PROPERTY(QColor itemBackgroundHover READ itemBackgroundHover WRITE setItemBackgroundHover)
Q_PROPERTY(QColor itemBackgroundSelected READ itemBackgroundSelected WRITE setItemBackgroundSelected)
Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor)
Q_PROPERTY(QColor titleColorSelected READ titleColorSelected WRITE setTitleColorSelected)
Q_PROPERTY(QColor subtitleColor READ subtitleColor WRITE setSubtitleColor)
Q_PROPERTY(QColor subtitleColorSelected READ subtitleColorSelected WRITE setSubtitleColorSelected)
QColor m_itemBackground = QColor(42, 42, 42);
QColor m_itemBackgroundHover = QColor(50, 50, 50);
QColor m_itemBackgroundSelected = QColor(11, 89, 45);
QColor m_titleColor = QColor(190, 190, 190);
QColor m_titleColorSelected = QColor(200, 200, 200);
QColor m_subtitleColor = QColor(140, 140, 140);
QColor m_subtitleColorSelected = QColor(180, 180, 180);
public:
enum DataRole { TitleRole = Qt::UserRole, TextRole = Qt::UserRole + 1 };
explicit CardListWidget(QWidget *parent = nullptr);
void addCard(const QString &title, const QString &text);
void updateColorsFromPalette();
QColor itemBackground() const { return m_itemBackground; }
void setItemBackground(const QColor &color) { m_itemBackground = color; }
QColor itemBackgroundHover() const { return m_itemBackgroundHover; }
void setItemBackgroundHover(const QColor &color) { m_itemBackgroundHover = color; }
QColor itemBackgroundSelected() const { return m_itemBackgroundSelected; }
void setItemBackgroundSelected(const QColor &color) { m_itemBackgroundSelected = color; }
QColor titleColor() const { return m_titleColor; }
void setTitleColor(const QColor &color) { m_titleColor = color; }
QColor titleColorSelected() const { return m_titleColorSelected; }
void setTitleColorSelected(const QColor &color) { m_titleColorSelected = color; }
QColor subtitleColor() const { return m_subtitleColor; }
void setSubtitleColor(const QColor &color) { m_subtitleColor = color; }
QColor subtitleColorSelected() const { return m_subtitleColorSelected; }
void setSubtitleColorSelected(const QColor &color) { m_subtitleColorSelected = color; }
};
class CardListDelegate : public QStyledItemDelegate
{
public:
explicit CardListDelegate(QObject *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
};
class ListDelegate : public QStyledItemDelegate {
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QLineEdit* editor = new QLineEdit(parent);
editor->setContentsMargins(1, 1, 1, 1);
return editor;
}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QSize size = QStyledItemDelegate::sizeHint(option, index);
return QSize(size.width(), size.height() + 4);
}
};
class PaddingDelegate : public QStyledItemDelegate {
public:
explicit PaddingDelegate(QObject* parent = nullptr)
: QStyledItemDelegate(parent), m_padding(4) {}
explicit PaddingDelegate(int padding, QObject* parent = nullptr)
: QStyledItemDelegate(parent), m_padding(padding) {}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QSize size = QStyledItemDelegate::sizeHint(option, index);
return QSize(size.width() + m_padding * 2, size.height() + m_padding);
}
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QVariant bgVar = index.data(Qt::BackgroundRole);
if (bgVar.isValid())
painter->fillRect(opt.rect, bgVar.value<QBrush>());
opt.state &= ~QStyle::State_HasFocus;
const QWidget* widget = option.widget;
QStyle* style = widget ? widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);
if (!opt.icon.isNull()) {
QRect iconRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &opt, widget);
opt.icon.paint(painter, iconRect, opt.decorationAlignment,
opt.state & QStyle::State_Enabled ? QIcon::Normal : QIcon::Disabled,
opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off);
}
if (!opt.text.isEmpty()) {
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget);
textRect.adjust(m_padding, 0, -m_padding, 0);
painter->save();
if (opt.state & QStyle::State_Selected)
painter->setPen(opt.palette.highlightedText().color());
else {
QVariant fgVar = index.data(Qt::ForegroundRole);
if (fgVar.isValid())
painter->setPen(fgVar.value<QColor>());
else
painter->setPen(opt.palette.text().color());
}
painter->setFont(opt.font);
QString elidedText = opt.fontMetrics.elidedText(opt.text, Qt::ElideRight, textRect.width());
painter->drawText(textRect, Qt::AlignVCenter | int(opt.displayAlignment & Qt::AlignHorizontal_Mask), elidedText);
painter->restore();
}
}
protected:
int m_padding;
};
class WrapAnywhereDelegate : public PaddingDelegate {
static constexpr int maxLines = 5;
public:
explicit WrapAnywhereDelegate(QObject* parent = nullptr) : PaddingDelegate(parent) {}
explicit WrapAnywhereDelegate(int padding, QObject* parent = nullptr) : PaddingDelegate(padding, parent) {}
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
// Background (same as PaddingDelegate)
QVariant bgVar = index.data(Qt::BackgroundRole);
if (bgVar.isValid())
painter->fillRect(opt.rect, bgVar.value<QBrush>());
opt.state &= ~QStyle::State_HasFocus;
// Draw everything except text using style
const QWidget* widget = option.widget;
QStyle* style = widget ? widget->style() : QApplication::style();
// Draw background/selection
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget);
// Draw text with WrapAnywhere (max 5 lines)
painter->save();
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget);
textRect.adjust(m_padding, 0, -m_padding, 0);
QString text = opt.text;
QFontMetrics fm(opt.font);
int lineHeight = fm.height();
int maxHeight = lineHeight * maxLines;
// Truncate text if it exceeds maxLines
QString displayText = text;
QTextOption layoutOpt(Qt::Alignment(opt.displayAlignment));
layoutOpt.setWrapMode(QTextOption::WrapAnywhere);
QTextLayout layout(text, opt.font);
layout.setTextOption(layoutOpt);
layout.beginLayout();
int lineCount = 0;
int lastLineEnd = 0;
while (lineCount < maxLines) {
QTextLine line = layout.createLine();
if (!line.isValid()) break;
line.setLineWidth(textRect.width());
lastLineEnd = line.textStart() + line.textLength();
lineCount++;
}
layout.endLayout();
bool truncated = (lastLineEnd < text.length());
if (truncated) {
displayText = text.left(lastLineEnd).trimmed() + "...";
}
QTextOption textOption;
textOption.setWrapMode(QTextOption::WrapAnywhere);
textOption.setAlignment(Qt::Alignment(opt.displayAlignment));
if (opt.state & QStyle::State_Selected)
painter->setPen(opt.palette.highlightedText().color());
else {
QVariant fgVar = index.data(Qt::ForegroundRole);
if (fgVar.isValid())
painter->setPen(fgVar.value<QColor>());
else
painter->setPen(opt.palette.text().color());
}
painter->setFont(opt.font);
painter->drawText(textRect, displayText, textOption);
painter->restore();
}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override {
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QString text = index.data(Qt::DisplayRole).toString();
int width = opt.rect.width() > 0 ? opt.rect.width() - 8 : 100;
QFontMetrics fm(opt.font);
int lineHeight = fm.height();
QRect bound = fm.boundingRect(QRect(0, 0, width, 10000), Qt::TextWrapAnywhere, text);
int maxHeight = lineHeight * maxLines + 8;
return QSize(opt.rect.width(), qMin(bound.height() + 8, maxHeight));
}
QString displayText(const QVariant& value, const QLocale& locale) const override {
return PaddingDelegate::displayText(value, locale);
}
bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) override {
if (event->type() == QEvent::ToolTip) {
QString text = index.data(Qt::DisplayRole).toString();
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QFontMetrics fm(opt.font);
int width = opt.rect.width() - 8;
if (width <= 0) width = 100;
QTextOption layoutOpt;
layoutOpt.setWrapMode(QTextOption::WrapAnywhere);
QTextLayout layout(text, opt.font);
layout.setTextOption(layoutOpt);
layout.beginLayout();
int lineCount = 0;
while (true) {
QTextLine line = layout.createLine();
if (!line.isValid()) break;
line.setLineWidth(width);
lineCount++;
}
layout.endLayout();
if (lineCount > maxLines) {
// Wrap text manually to limit tooltip width
QFontMetrics tooltipFm(QToolTip::font());
QString wrappedText;
int maxWidth = 1000;
int pos = 0;
while (pos < text.length()) {
int lineEnd = text.indexOf('\n', pos);
if (lineEnd == -1) lineEnd = text.length();
QString line = text.mid(pos, lineEnd - pos);
while (!line.isEmpty()) {
QString chunk;
int i = 1;
while (i <= line.length() && tooltipFm.horizontalAdvance(line.left(i)) < maxWidth)
i++;
chunk = line.left(i - 1);
if (chunk.isEmpty()) chunk = line.left(1);
wrappedText += chunk.toHtmlEscaped() + "<br>";
line = line.mid(chunk.length());
}
pos = lineEnd + 1;
}
if (wrappedText.endsWith("<br>"))
wrappedText.chop(4);
QToolTip::showText(event->globalPos(), wrappedText, view);
return true;
}
}
return PaddingDelegate::helpEvent(event, view, option, index);
}
};
class SpinTable : public QWidget {
Q_OBJECT
public:
QGridLayout* layout = nullptr;
QTableView* table = nullptr;
QStandardItemModel* tableModel = nullptr;
QPushButton* buttonAdd = nullptr;
QPushButton* buttonClear = nullptr;
SpinTable(int rows, int clomuns, QWidget* parent);
explicit SpinTable(QWidget* parent = nullptr) { SpinTable(0,0,parent); }
~SpinTable() override = default;
};
class ClickableLabel : public QLabel {
Q_OBJECT
public:
explicit ClickableLabel(const QString &label, QWidget *parent = nullptr) : QLabel(label, parent) {}
Q_SIGNALS:
void clicked();
protected:
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
Q_EMIT clicked();
}
QLabel::mousePressEvent(event);
}
};
struct FormatRange {
int start;
int length;
QTextCharFormat format;
};
class ConsoleBlockData : public QTextBlockUserData {
public:
QVector<FormatRange> formats;
};
class ConsoleHighlighter : public QSyntaxHighlighter {
public:
explicit ConsoleHighlighter(QTextDocument* parent);
protected:
void highlightBlock(const QString& text) override;
};
class TextEditConsole : public QPlainTextEdit {
Q_OBJECT
friend class ConsoleHighlighter;
struct FormattedChunk {
QString text;
QTextCharFormat format;
};
ConsoleHighlighter* highlighter = nullptr;
QTextCursor cachedCursor;
int maxLines = 50000;
bool autoScroll = false;
bool noWrap = true;
bool syncMode = false;
bool showBgImage = true;
int appendCount = 0;
QPixmap m_bgPixmap;
QColor m_bgColor = QColor("#151515");
int m_bgDimming = 70;
bool m_hasBgImage = false;
void updatePaletteBackground();
QList<FormattedChunk> pendingChunks;
QTimer* batchTimer = nullptr;
QMutex* batchMutex = nullptr;
static constexpr int BATCH_INTERVAL_MS = 100;
static constexpr int MAX_BATCH_SIZE = 64 * 1024;
int pendingSize = 0;
void trimExcessLines();
void createContextMenu(const QPoint &pos);
void setBufferSize(int size);
void appendChunk(const QString& text, const QTextCharFormat& fmt);
void insertChunks(const QList<FormattedChunk>& chunks);
private Q_SLOTS:
void flushPending();
public:
explicit TextEditConsole(QWidget* parent = nullptr, int maxLines = 50000, bool noWrap = true, bool autoScroll = false);
void appendPlain(const QString& text);
void appendFormatted(const QString& text, const std::function<void(QTextCharFormat&)> &styleFn);
void appendColor(const QString& text, QColor color);
void appendBold(const QString& text);
void appendUnderline(const QString& text);
void appendColorBold(const QString& text, QColor color);
void appendColorUnderline(const QString& text, QColor color);
void setMaxLines(int lines);
void setAutoScrollEnabled(bool enabled);
bool isAutoScrollEnabled() const;
bool isNoWrapEnabled() const;
void setSyncMode(bool enabled);
void flushAll();
bool isShowBackgroundImage() const;
void setShowBackgroundImage(bool enabled);
void setConsoleBackground(const QColor& bgColor, const QString& imagePath = QString(), int dimming = 70);
protected:
void paintEvent(QPaintEvent* event) override;
Q_SIGNALS:
void ctx_find();
void ctx_history();
void ctx_bgToggled(bool showImage);
};
#endif