2025-12-27 09:34:02 +08:00

492 lines
15 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 "ImageGridWidget.h"
#include <QGridLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QResizeEvent>
#include <QShowEvent>
#include <QStackedWidget>
#include <QScrollBar>
#include <QKeyEvent>
#include <QPushButton>
#include <cmath>
// ImageTileWidget 实现
ImageTileWidget::ImageTileWidget(QWidget* parent)
: QWidget(parent)
{
setMinimumSize(100, 100);
setCursor(Qt::PointingHandCursor);
}
void ImageTileWidget::setImage(const QImage& image)
{
m_image = image;
update();
}
void ImageTileWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制背景
painter.fillRect(rect(), QColor(47, 48, 52));
if (!m_image.isNull()) {
// 缩放图像以适应控件
QImage scaled = m_image.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
int x = (width() - scaled.width()) / 2;
int y = (height() - scaled.height()) / 2;
painter.drawImage(x, y, scaled);
// 在左上角显示设备名称(无背景)
if (!m_alias.isEmpty()) {
QFont font("Arial", 14);
painter.setFont(font);
painter.setPen(Qt::white);
painter.drawText(10, 25, m_alias);
}
} else {
// 显示占位符
QFont font("Arial", 14);
painter.setFont(font);
painter.setPen(Qt::white);
painter.drawText(rect(), Qt::AlignCenter, m_alias.isEmpty() ? "无图像" : m_alias);
}
}
void ImageTileWidget::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
emit clicked();
} else if (event->button() == Qt::RightButton) {
emit rightClicked();
}
}
// ImageZoomWidget 实现
ImageZoomWidget::ImageZoomWidget(QWidget* parent)
: QWidget(parent)
{
setStyleSheet("background-color: rgb(25, 26, 28);");
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(10, 10, 10, 10);
mainLayout->setSpacing(10);
// 顶部标题栏
QHBoxLayout* topLayout = new QHBoxLayout();
m_titleLabel = new QLabel(this);
m_titleLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; font-weight: bold; background: transparent; }");
topLayout->addWidget(m_titleLabel);
topLayout->addStretch();
// 返回按钮
QPushButton* btnBack = new QPushButton(QString::fromUtf8("返回"), this);
btnBack->setFixedSize(80, 30);
btnBack->setStyleSheet(
"QPushButton { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); "
"border: 1px solid rgb(80, 82, 88); border-radius: 5px; font-size: 14px; }"
"QPushButton:hover { background-color: rgb(80, 82, 88); }"
"QPushButton:pressed { background-color: rgb(50, 52, 58); }"
);
connect(btnBack, &QPushButton::clicked, this, &ImageZoomWidget::exitRequested);
topLayout->addWidget(btnBack);
mainLayout->addLayout(topLayout);
// 滚动区域
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(false);
m_scrollArea->setAlignment(Qt::AlignCenter);
m_scrollArea->setStyleSheet(
"QScrollArea { background-color: rgb(38, 40, 47); border: none; }"
"QScrollBar:vertical { background-color: rgb(38, 40, 47); width: 12px; }"
"QScrollBar::handle:vertical { background-color: rgb(80, 82, 88); border-radius: 6px; min-height: 20px; }"
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
"QScrollBar:horizontal { background-color: rgb(38, 40, 47); height: 12px; }"
"QScrollBar::handle:horizontal { background-color: rgb(80, 82, 88); border-radius: 6px; min-width: 20px; }"
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
);
m_imageLabel = new QLabel(m_scrollArea);
m_imageLabel->setAlignment(Qt::AlignCenter);
m_imageLabel->setStyleSheet("background-color: rgb(38, 40, 47);");
m_imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
m_imageLabel->setScaledContents(false);
m_scrollArea->setWidget(m_imageLabel);
mainLayout->addWidget(m_scrollArea, 1);
// 底部信息栏
m_infoLabel = new QLabel(this);
m_infoLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 12px; background: transparent; }");
m_infoLabel->setText(QString::fromUtf8("滚轮缩放 | 拖拽平移 | 双击还原 | 点击返回按钮退出"));
mainLayout->addWidget(m_infoLabel);
setFocusPolicy(Qt::StrongFocus);
}
void ImageZoomWidget::setImage(const QImage& image, const QString& title)
{
m_originalImage = image;
m_titleLabel->setText(title);
m_needResetZoom = true;
// 先显示图像showEvent 中会重新计算缩放
updateImageDisplay();
}
void ImageZoomWidget::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
if (m_needResetZoom && !m_originalImage.isNull()) {
m_needResetZoom = false;
// 延迟一帧执行,确保布局完成
QMetaObject::invokeMethod(this, &ImageZoomWidget::resetZoom, Qt::QueuedConnection);
}
}
void ImageZoomWidget::updateImageDisplay()
{
if (m_originalImage.isNull()) {
m_imageLabel->setText(QString::fromUtf8("无图像"));
return;
}
int newWidth = static_cast<int>(m_originalImage.width() * m_scaleFactor);
int newHeight = static_cast<int>(m_originalImage.height() * m_scaleFactor);
QImage scaledImage = m_originalImage.scaled(newWidth, newHeight,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
m_imageLabel->setPixmap(QPixmap::fromImage(scaledImage));
m_imageLabel->resize(scaledImage.size());
QString info = QString::fromUtf8("缩放: %1% | 尺寸: %2x%3 | 滚轮缩放 | 拖拽平移 | 双击还原")
.arg(static_cast<int>(m_scaleFactor * 100))
.arg(m_originalImage.width())
.arg(m_originalImage.height());
m_infoLabel->setText(info);
}
void ImageZoomWidget::resetZoom()
{
if (m_originalImage.isNull()) return;
QSize viewSize = m_scrollArea->viewport()->size();
double scaleX = static_cast<double>(viewSize.width() - 20) / m_originalImage.width();
double scaleY = static_cast<double>(viewSize.height() - 20) / m_originalImage.height();
m_scaleFactor = qMin(scaleX, scaleY);
m_scaleFactor = qMax(m_scaleFactor, MIN_SCALE);
updateImageDisplay();
m_scrollArea->horizontalScrollBar()->setValue(
(m_imageLabel->width() - m_scrollArea->viewport()->width()) / 2);
m_scrollArea->verticalScrollBar()->setValue(
(m_imageLabel->height() - m_scrollArea->viewport()->height()) / 2);
}
void ImageZoomWidget::wheelEvent(QWheelEvent* event)
{
if (m_originalImage.isNull()) return;
double delta = event->angleDelta().y() > 0 ? SCALE_STEP : -SCALE_STEP;
double newScale = m_scaleFactor + delta;
newScale = qMax(newScale, MIN_SCALE);
newScale = qMin(newScale, MAX_SCALE);
if (qAbs(newScale - m_scaleFactor) > 0.001) {
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
double hRatio = hBar->maximum() > 0 ? static_cast<double>(hBar->value()) / hBar->maximum() : 0.5;
double vRatio = vBar->maximum() > 0 ? static_cast<double>(vBar->value()) / vBar->maximum() : 0.5;
m_scaleFactor = newScale;
updateImageDisplay();
hBar->setValue(static_cast<int>(hRatio * hBar->maximum()));
vBar->setValue(static_cast<int>(vRatio * vBar->maximum()));
}
event->accept();
}
void ImageZoomWidget::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = true;
m_lastPos = event->pos();
setCursor(Qt::ClosedHandCursor);
}
QWidget::mousePressEvent(event);
}
void ImageZoomWidget::mouseMoveEvent(QMouseEvent* event)
{
if (m_dragging) {
QPoint delta = event->pos() - m_lastPos;
m_lastPos = event->pos();
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
hBar->setValue(hBar->value() - delta.x());
vBar->setValue(vBar->value() - delta.y());
}
QWidget::mouseMoveEvent(event);
}
void ImageZoomWidget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = false;
setCursor(Qt::ArrowCursor);
}
QWidget::mouseReleaseEvent(event);
}
void ImageZoomWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
resetZoom();
}
QWidget::mouseDoubleClickEvent(event);
}
void ImageZoomWidget::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_Escape:
emit exitRequested();
break;
case Qt::Key_Plus:
case Qt::Key_Equal:
m_scaleFactor = qMin(m_scaleFactor + SCALE_STEP, MAX_SCALE);
updateImageDisplay();
break;
case Qt::Key_Minus:
m_scaleFactor = qMax(m_scaleFactor - SCALE_STEP, MIN_SCALE);
updateImageDisplay();
break;
case Qt::Key_0:
m_scaleFactor = 1.0;
updateImageDisplay();
break;
case Qt::Key_F:
resetZoom();
break;
default:
QWidget::keyPressEvent(event);
}
}
void ImageZoomWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (!m_originalImage.isNull()) {
resetZoom();
}
}
// ImageGridWidget 实现
ImageGridWidget::ImageGridWidget(QWidget* parent)
: QWidget(parent)
{
// 使用 QStackedWidget 切换网格视图和放大视图
m_stackedWidget = new QStackedWidget(this);
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addWidget(m_stackedWidget);
// 网格容器
m_gridContainer = new QWidget(this);
m_layout = new QGridLayout(m_gridContainer);
m_layout->setSpacing(5);
m_layout->setContentsMargins(5, 5, 5, 5);
// 创建无图像提示标签
m_noImageLabel = new QLabel("暂无图像", m_gridContainer);
m_noImageLabel->setAlignment(Qt::AlignCenter);
m_noImageLabel->setStyleSheet("color: rgb(150, 150, 150); font-size: 24px;");
m_noImageLabel->hide();
m_stackedWidget->addWidget(m_gridContainer);
// 放大视图
m_zoomWidget = new ImageZoomWidget(this);
connect(m_zoomWidget, &ImageZoomWidget::exitRequested, this, &ImageGridWidget::showGridView);
m_stackedWidget->addWidget(m_zoomWidget);
// 默认显示网格视图
m_stackedWidget->setCurrentIndex(0);
}
void ImageGridWidget::initImages(int count)
{
// 清除现有的tiles
for (auto tile : m_tiles) {
m_layout->removeWidget(tile);
delete tile;
}
m_tiles.clear();
m_aliasMap.clear();
if (count <= 0) {
m_noImageLabel->show();
m_columns = 0;
m_rows = 0;
return;
}
m_noImageLabel->hide();
// 计算行列数
m_columns = static_cast<int>(std::ceil(std::sqrt(count)));
m_rows = static_cast<int>(std::ceil(static_cast<double>(count) / m_columns));
// 创建新的tiles
for (int i = 0; i < count; ++i) {
auto tile = new ImageTileWidget(m_gridContainer);
m_tiles.append(tile);
int row = i / m_columns;
int col = i % m_columns;
m_layout->addWidget(tile, row, col);
connect(tile, &ImageTileWidget::clicked, [this, i]() {
// 点击时显示放大视图
showZoomView(i);
emit tileClicked(i);
});
connect(tile, &ImageTileWidget::rightClicked, [this, i]() {
// 右键点击时发射信号,携带索引和别名
QString alias = m_tiles[i]->alias();
emit tileRightClicked(i, alias);
});
}
updateTileSizes();
}
void ImageGridWidget::setImages(int index, const QImage& image)
{
if (index >= 0 && index < m_tiles.size()) {
m_tiles[index]->setImage(image);
}
}
void ImageGridWidget::setImages(const QString& alias, const QImage& image)
{
if (m_aliasMap.contains(alias)) {
int index = m_aliasMap[alias];
setImages(index, image);
}
}
void ImageGridWidget::setTileAlias(int index, const QString& alias)
{
if (index >= 0 && index < m_tiles.size()) {
m_tiles[index]->setAlias(alias);
m_aliasMap[alias] = index;
}
}
void ImageGridWidget::setSelectedIndex(int index)
{
m_selectedIndex = index;
}
void ImageGridWidget::setExpandedIndex(int index)
{
m_expandedIndex = index;
updateTileSizes();
}
void ImageGridWidget::rebuildGrid()
{
// 重新构建网格布局
for (int i = 0; i < m_tiles.size(); ++i) {
int row = i / m_columns;
int col = i % m_columns;
m_layout->addWidget(m_tiles[i], row, col);
}
}
void ImageGridWidget::updateTileSizes()
{
if (m_tiles.isEmpty() || m_columns == 0 || m_rows == 0) {
return;
}
// 使用 stackedWidget 的尺寸,因为 gridContainer 在 stackedWidget 内
int containerWidth = m_stackedWidget->width();
int containerHeight = m_stackedWidget->height();
int availableWidth = containerWidth - m_layout->contentsMargins().left() - m_layout->contentsMargins().right()
- (m_columns - 1) * m_layout->spacing();
int availableHeight = containerHeight - m_layout->contentsMargins().top() - m_layout->contentsMargins().bottom()
- (m_rows - 1) * m_layout->spacing();
int tileWidth = availableWidth / m_columns;
int tileHeight = availableHeight / m_rows;
for (auto tile : m_tiles) {
tile->setFixedSize(tileWidth, tileHeight);
}
}
void ImageGridWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateTileSizes();
}
QImage ImageGridWidget::getImage(int index) const
{
if (index >= 0 && index < m_tiles.size()) {
return m_tiles[index]->image();
}
return QImage();
}
QString ImageGridWidget::getAlias(int index) const
{
if (index >= 0 && index < m_tiles.size()) {
return m_tiles[index]->alias();
}
return QString();
}
void ImageGridWidget::showZoomView(int index)
{
if (index < 0 || index >= m_tiles.size()) {
return;
}
QImage image = m_tiles[index]->image();
if (image.isNull()) {
return;
}
QString alias = m_tiles[index]->alias();
m_zoomWidget->setImage(image, alias);
m_stackedWidget->setCurrentIndex(1);
m_zoomWidget->setFocus();
}
void ImageGridWidget::showGridView()
{
m_stackedWidget->setCurrentIndex(0);
}