GrabBag/AppUtils/UICommon/Src/DetectImageWidget.cpp

448 lines
14 KiB
C++

#include "DetectImageWidget.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QScrollBar>
#include <QWheelEvent>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QResizeEvent>
#include <QShowEvent>
#include <QEvent>
#include <QGraphicsPixmapItem>
#include <QFileDialog>
#include <QStandardPaths>
#include <QDir>
#include <QDateTime>
#include <cmath>
// ============================================================================
// ImageZoomView 实现
// ============================================================================
ImageZoomView::ImageZoomView(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, &ImageZoomView::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 ImageZoomView::setImage(const QImage& image, const QString& title)
{
m_originalImage = image;
m_titleLabel->setText(title);
m_needResetZoom = true;
updateImageDisplay();
}
void ImageZoomView::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
if (m_needResetZoom && !m_originalImage.isNull()) {
m_needResetZoom = false;
QMetaObject::invokeMethod(this, &ImageZoomView::resetZoom, Qt::QueuedConnection);
}
}
void ImageZoomView::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 ImageZoomView::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 ImageZoomView::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 ImageZoomView::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = true;
m_lastPos = event->pos();
setCursor(Qt::ClosedHandCursor);
}
QWidget::mousePressEvent(event);
}
void ImageZoomView::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 ImageZoomView::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_dragging = false;
setCursor(Qt::ArrowCursor);
}
QWidget::mouseReleaseEvent(event);
}
void ImageZoomView::mouseDoubleClickEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
resetZoom();
}
QWidget::mouseDoubleClickEvent(event);
}
void ImageZoomView::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 ImageZoomView::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (!m_originalImage.isNull()) {
resetZoom();
}
}
// ============================================================================
// DetectImageWidget 实现
// ============================================================================
DetectImageWidget::DetectImageWidget(QWidget* parent)
: QWidget(parent)
{
setupUI();
}
DetectImageWidget::~DetectImageWidget()
{
}
void DetectImageWidget::setupUI()
{
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// 使用 QStackedWidget 切换普通视图和放大视图
m_stackedWidget = new QStackedWidget(this);
// 普通视图 - QGraphicsView
m_graphicsView = new QGraphicsView(this);
m_scene = new QGraphicsScene(this);
m_graphicsView->setScene(m_scene);
m_graphicsView->setRenderHint(QPainter::Antialiasing);
m_graphicsView->setRenderHint(QPainter::SmoothPixmapTransform);
m_graphicsView->setDragMode(QGraphicsView::NoDrag);
m_graphicsView->setStyleSheet("QGraphicsView { background-color: rgb(47, 48, 52); border: none; }");
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setCursor(Qt::PointingHandCursor);
// 设置点击事件过滤
m_graphicsView->viewport()->installEventFilter(this);
m_stackedWidget->addWidget(m_graphicsView);
// 放大视图
m_zoomView = new ImageZoomView(this);
connect(m_zoomView, &ImageZoomView::exitRequested, this, &DetectImageWidget::showNormalView);
m_stackedWidget->addWidget(m_zoomView);
mainLayout->addWidget(m_stackedWidget);
// 默认显示普通视图
m_stackedWidget->setCurrentIndex(0);
// 设置右键菜单
m_graphicsView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(m_graphicsView, &QGraphicsView::customContextMenuRequested,
this, &DetectImageWidget::showContextMenu);
// 创建右键菜单
m_contextMenu = new QMenu(this);
m_saveDataAction = new QAction(QString::fromUtf8("保存检测数据"), this);
m_saveDataAction->setEnabled(false);
// 设置菜单样式
m_contextMenu->setStyleSheet(
"QMenu { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); border: 1px solid rgb(80, 82, 88); }"
"QMenu::item { padding: 8px 25px; }"
"QMenu::item:selected { background-color: rgb(80, 82, 88); }"
"QMenu::item:disabled { color: rgb(120, 120, 120); }"
);
m_contextMenu->addAction(m_saveDataAction);
connect(m_saveDataAction, &QAction::triggered, this, &DetectImageWidget::onSaveDataTriggered);
}
bool DetectImageWidget::eventFilter(QObject* obj, QEvent* event)
{
if (obj == m_graphicsView->viewport()) {
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (mouseEvent->button() == Qt::LeftButton) {
// 点击放大
if (!m_image.isNull()) {
showZoomView();
emit clicked();
}
return true;
}
}
}
return QWidget::eventFilter(obj, event);
}
void DetectImageWidget::setImage(const QImage& image)
{
m_image = image;
updateImageDisplay();
}
QImage DetectImageWidget::getImage() const
{
return m_image;
}
void DetectImageWidget::clearImage()
{
m_image = QImage();
m_scene->clear();
}
void DetectImageWidget::setTitle(const QString& title)
{
m_title = title;
}
void DetectImageWidget::setSaveDataEnabled(bool enabled)
{
m_saveDataEnabled = enabled;
m_saveDataAction->setEnabled(enabled);
}
void DetectImageWidget::setSaveDataCallback(const SaveDataCallback& callback)
{
m_saveDataCallback = callback;
}
void DetectImageWidget::setSaveDataMenuText(const QString& text)
{
m_saveDataAction->setText(text);
}
void DetectImageWidget::showZoomView()
{
if (m_image.isNull()) {
return;
}
m_zoomView->setImage(m_image, m_title);
m_stackedWidget->setCurrentIndex(1);
m_zoomView->setFocus();
}
void DetectImageWidget::showNormalView()
{
m_stackedWidget->setCurrentIndex(0);
}
void DetectImageWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
updateImageDisplay();
}
void DetectImageWidget::updateImageDisplay()
{
if (m_image.isNull()) {
return;
}
m_scene->clear();
QPixmap pixmap = QPixmap::fromImage(m_image);
m_scene->addPixmap(pixmap);
m_scene->setSceneRect(pixmap.rect());
// 自适应显示
m_graphicsView->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio);
}
void DetectImageWidget::showContextMenu(const QPoint& pos)
{
// 检查是否有数据可保存
if (!m_saveDataEnabled) {
return;
}
m_contextMenu->exec(m_graphicsView->mapToGlobal(pos));
}
void DetectImageWidget::onSaveDataTriggered()
{
// 发射信号,让外部处理保存逻辑
emit saveDataRequested(this);
// 如果设置了回调,也调用回调
if (m_saveDataCallback) {
// 获取默认保存路径
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
QString defaultFileName = QString("%1/detect_data_%2.pcd").arg(defaultPath).arg(timestamp);
QString filePath = QFileDialog::getSaveFileName(
this,
QString::fromUtf8("保存检测数据"),
defaultFileName,
QString::fromUtf8("点云文件 (*.pcd);;所有文件 (*.*)")
);
if (!filePath.isEmpty()) {
m_saveDataCallback(filePath);
}
}
}