2025-08-24 23:24:33 +08:00
|
|
|
|
#include "ImageGridWidget.h"
|
|
|
|
|
|
#include "ImageTileWidget.h"
|
|
|
|
|
|
#include <QGridLayout>
|
|
|
|
|
|
#include <QtMath>
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
|
#include <QResizeEvent>
|
2025-08-31 21:08:28 +08:00
|
|
|
|
#include <QLabel>
|
2025-08-24 23:24:33 +08:00
|
|
|
|
|
|
|
|
|
|
ImageGridWidget::ImageGridWidget(QWidget* parent)
|
|
|
|
|
|
: QWidget(parent) {
|
|
|
|
|
|
m_layout = new QGridLayout(this);
|
2025-08-27 23:10:36 +08:00
|
|
|
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
2025-11-30 15:38:27 +08:00
|
|
|
|
m_layout->setHorizontalSpacing(4); // 左右保留间距
|
|
|
|
|
|
m_layout->setVerticalSpacing(0); // 上下无间距
|
2025-08-24 23:24:33 +08:00
|
|
|
|
setLayout(m_layout);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置控件大小策略为可扩展
|
|
|
|
|
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
2025-08-31 21:08:28 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加一个默认标签,当没有图像时显示
|
|
|
|
|
|
m_noImageLabel = new QLabel("暂无图像数据", this);
|
|
|
|
|
|
m_noImageLabel->setAlignment(Qt::AlignCenter);
|
|
|
|
|
|
m_noImageLabel->setStyleSheet("color: gray; font-size: 18px;");
|
|
|
|
|
|
m_noImageLabel->hide();
|
2025-08-24 23:24:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ImageGridWidget::setImages(int index, const QImage& image) {
|
|
|
|
|
|
if (index < 0 || index >= m_tiles.size()) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 直接调用ImageTileWidget的setImage方法
|
|
|
|
|
|
m_tiles[index]->setImage(image);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-31 21:08:28 +08:00
|
|
|
|
// 通过别名设置图像
|
|
|
|
|
|
void ImageGridWidget::setImages(const QString& alias, const QImage& image) {
|
|
|
|
|
|
if (m_aliasMap.contains(alias)) {
|
|
|
|
|
|
int index = m_aliasMap[alias];
|
|
|
|
|
|
setImages(index, image);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-24 23:24:33 +08:00
|
|
|
|
void ImageGridWidget::initImages(int count) {
|
|
|
|
|
|
m_paths.clear();
|
|
|
|
|
|
m_selectedIndex = -1; // 默认无选中,所有格子等大
|
|
|
|
|
|
m_expandedIndex = -1; // 默认无展开
|
2025-08-31 21:08:28 +08:00
|
|
|
|
m_aliasMap.clear(); // 清空别名映射
|
2025-08-24 23:24:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 清空现有布局
|
|
|
|
|
|
QLayoutItem* child;
|
|
|
|
|
|
while ((child = m_layout->takeAt(0)) != nullptr) {
|
2025-08-31 21:08:28 +08:00
|
|
|
|
if (child->widget()) {
|
|
|
|
|
|
child->widget()->deleteLater();
|
|
|
|
|
|
}
|
2025-08-24 23:24:33 +08:00
|
|
|
|
delete child;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_tiles.clear();
|
|
|
|
|
|
|
2025-08-31 21:08:28 +08:00
|
|
|
|
// 如果count为0,显示提示信息
|
|
|
|
|
|
if (count <= 0) {
|
|
|
|
|
|
m_layout->addWidget(m_noImageLabel, 0, 0, Qt::AlignCenter);
|
|
|
|
|
|
m_noImageLabel->show();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
m_noImageLabel->hide();
|
|
|
|
|
|
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// 初始化指定数量的格子,改为竖向布局,最多2行
|
|
|
|
|
|
m_rows = qMax(1, qMin(2, count));
|
|
|
|
|
|
m_columns = (count + m_rows - 1) / m_rows;
|
|
|
|
|
|
|
2025-08-24 23:24:33 +08:00
|
|
|
|
for (int i = 0; i < count; ++i) {
|
|
|
|
|
|
ImageTileWidget* tile = new ImageTileWidget(this);
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// 列优先布局:先填满一列再填下一列
|
|
|
|
|
|
int c = i / m_rows;
|
|
|
|
|
|
int r = i % m_rows;
|
|
|
|
|
|
|
|
|
|
|
|
// 奇数排(行号为偶数)图片靠下显示,偶数排(行号为奇数)图片靠上显示
|
|
|
|
|
|
Qt::Alignment imageAlign = (r % 2 == 0) ? Qt::AlignBottom : Qt::AlignTop;
|
|
|
|
|
|
tile->setImageAlignment(imageAlign);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置固定尺寸
|
|
|
|
|
|
tile->setFixedSize(m_sizeNormal);
|
|
|
|
|
|
tile->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
|
|
|
|
|
|
|
|
|
|
// 控件在网格中居中显示
|
|
|
|
|
|
m_layout->addWidget(tile, r, c, Qt::AlignCenter);
|
2025-08-24 23:24:33 +08:00
|
|
|
|
m_tiles.append(tile);
|
|
|
|
|
|
|
|
|
|
|
|
connect(tile, &ImageTileWidget::clicked, this, [this, i]() {
|
|
|
|
|
|
if (m_expandedIndex == i) {
|
|
|
|
|
|
// 如果点击的是已展开的格子,不触发选中,只触发点击事件
|
|
|
|
|
|
emit tileClicked(i);
|
|
|
|
|
|
} else if (m_expandedIndex == -1) {
|
|
|
|
|
|
// 如果没有展开的格子,设置选中并展开
|
|
|
|
|
|
setExpandedIndex(i);
|
|
|
|
|
|
emit tileClicked(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
connect(tile, &ImageTileWidget::shrinkRequested, this, [this, i]() {
|
|
|
|
|
|
if (m_expandedIndex == i) {
|
|
|
|
|
|
setExpandedIndex(-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateTileSizes();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-31 21:08:28 +08:00
|
|
|
|
// 设置别名
|
|
|
|
|
|
void ImageGridWidget::setTileAlias(int index, const QString& alias) {
|
|
|
|
|
|
if (index < 0 || index >= m_tiles.size()) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置tile的别名
|
|
|
|
|
|
m_tiles[index]->setAlias(alias);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新别名到索引的映射
|
|
|
|
|
|
m_aliasMap[alias] = index;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-24 23:24:33 +08:00
|
|
|
|
void ImageGridWidget::setSelectedIndex(int index) {
|
|
|
|
|
|
if (index < -1 || index >= m_tiles.size()) return;
|
|
|
|
|
|
m_selectedIndex = index;
|
|
|
|
|
|
updateTileSizes();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ImageGridWidget::setExpandedIndex(int index) {
|
|
|
|
|
|
if (index < -1 || index >= m_tiles.size()) return;
|
|
|
|
|
|
m_expandedIndex = index;
|
|
|
|
|
|
updateTileSizes();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ImageGridWidget::rebuildGrid() {
|
|
|
|
|
|
// Clear existing
|
|
|
|
|
|
QLayoutItem* child;
|
|
|
|
|
|
while ((child = m_layout->takeAt(0)) != nullptr) {
|
|
|
|
|
|
if (child->widget()) child->widget()->deleteLater();
|
|
|
|
|
|
delete child;
|
|
|
|
|
|
}
|
|
|
|
|
|
m_tiles.clear();
|
|
|
|
|
|
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// Determine grid size: up to 2 rows (vertical layout)
|
2025-08-24 23:24:33 +08:00
|
|
|
|
int n = m_paths.size();
|
2025-11-30 15:38:27 +08:00
|
|
|
|
m_rows = qMax(1, qMin(2, n));
|
|
|
|
|
|
m_columns = (n + m_rows - 1) / m_rows;
|
2025-08-24 23:24:33 +08:00
|
|
|
|
|
2025-08-31 21:08:28 +08:00
|
|
|
|
// 如果没有路径,显示提示信息
|
|
|
|
|
|
if (n <= 0) {
|
|
|
|
|
|
m_layout->addWidget(m_noImageLabel, 0, 0, Qt::AlignCenter);
|
|
|
|
|
|
m_noImageLabel->show();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-30 15:38:27 +08:00
|
|
|
|
|
2025-08-31 21:08:28 +08:00
|
|
|
|
m_noImageLabel->hide();
|
|
|
|
|
|
|
2025-08-24 23:24:33 +08:00
|
|
|
|
for (int i = 0; i < n; ++i) {
|
|
|
|
|
|
ImageTileWidget* tile = new ImageTileWidget(this);
|
|
|
|
|
|
tile->setImagePath(m_paths.at(i));
|
|
|
|
|
|
tile->setSelected(i == m_selectedIndex);
|
|
|
|
|
|
tile->setExpanded(i == m_expandedIndex);
|
2025-11-30 15:38:27 +08:00
|
|
|
|
|
|
|
|
|
|
// 列优先布局:先填满一列再填下一列
|
|
|
|
|
|
int c = i / m_rows;
|
|
|
|
|
|
int r = i % m_rows;
|
|
|
|
|
|
|
|
|
|
|
|
// 奇数排(行号为偶数)图片靠下显示,偶数排(行号为奇数)图片靠上显示
|
|
|
|
|
|
Qt::Alignment imageAlign = (r % 2 == 0) ? Qt::AlignBottom : Qt::AlignTop;
|
|
|
|
|
|
tile->setImageAlignment(imageAlign);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置固定尺寸,确保控件大小一致
|
|
|
|
|
|
tile->setFixedSize(m_sizeNormal);
|
|
|
|
|
|
tile->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
|
|
|
|
|
|
|
|
|
|
// 控件在网格中居中显示
|
|
|
|
|
|
m_layout->addWidget(tile, r, c, Qt::AlignCenter);
|
2025-08-24 23:24:33 +08:00
|
|
|
|
m_tiles.append(tile);
|
|
|
|
|
|
|
|
|
|
|
|
connect(tile, &ImageTileWidget::clicked, this, [this, i]() {
|
|
|
|
|
|
if (m_expandedIndex == i) {
|
|
|
|
|
|
// 如果点击的是已展开的格子,不触发选中,只触发点击事件
|
|
|
|
|
|
emit tileClicked(i);
|
|
|
|
|
|
} else if (m_expandedIndex == -1) {
|
|
|
|
|
|
// 如果没有展开的格子,设置选中并展开
|
|
|
|
|
|
setExpandedIndex(i);
|
|
|
|
|
|
emit tileClicked(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
connect(tile, &ImageTileWidget::shrinkRequested, this, [this, i]() {
|
|
|
|
|
|
if (m_expandedIndex == i) {
|
|
|
|
|
|
setExpandedIndex(-1);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updateTileSizes();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ImageGridWidget::resizeEvent(QResizeEvent* event) {
|
2025-08-27 23:10:36 +08:00
|
|
|
|
QWidget::resizeEvent(event);
|
2025-08-24 23:24:33 +08:00
|
|
|
|
|
2025-08-27 23:10:36 +08:00
|
|
|
|
// 防止递归调用,只在尺寸真正变化时更新
|
|
|
|
|
|
static QSize lastSize;
|
|
|
|
|
|
if (lastSize != event->size()) {
|
|
|
|
|
|
lastSize = event->size();
|
|
|
|
|
|
updateTileSizes();
|
|
|
|
|
|
}
|
2025-08-24 23:24:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ImageGridWidget::updateTileSizes() {
|
2025-08-31 21:08:28 +08:00
|
|
|
|
// 如果没有瓦片,直接返回
|
|
|
|
|
|
if (m_tiles.isEmpty()) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-24 23:24:33 +08:00
|
|
|
|
const bool anyExpanded = (m_expandedIndex >= 0 && m_expandedIndex < m_tiles.size());
|
|
|
|
|
|
const bool anySelected = (m_selectedIndex >= 0 && m_selectedIndex < m_tiles.size());
|
|
|
|
|
|
|
|
|
|
|
|
// 计算可用空间(减去边距和间距)
|
|
|
|
|
|
int availableWidth = width() - m_layout->contentsMargins().left() - m_layout->contentsMargins().right();
|
|
|
|
|
|
int availableHeight = height() - m_layout->contentsMargins().top() - m_layout->contentsMargins().bottom();
|
|
|
|
|
|
|
|
|
|
|
|
// 计算每个格子的基础尺寸(根据窗口大小动态调整)
|
2025-08-27 23:10:36 +08:00
|
|
|
|
// 确保减去的间距不会导致负数
|
|
|
|
|
|
int horizontalSpacingTotal = m_layout->horizontalSpacing() * (m_columns - 1);
|
|
|
|
|
|
int verticalSpacingTotal = m_layout->verticalSpacing() * (m_rows - 1);
|
|
|
|
|
|
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// 对于竖向布局,优先保证高度分配合理,因为最多只有2行
|
2025-08-27 23:10:36 +08:00
|
|
|
|
int baseTileWidth = qMax(100, (availableWidth - horizontalSpacingTotal) / m_columns);
|
|
|
|
|
|
int baseTileHeight = qMax(100, (availableHeight - verticalSpacingTotal) / m_rows);
|
2025-08-24 23:24:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据窗口大小动态调整尺寸
|
|
|
|
|
|
m_sizeNormal = QSize(baseTileWidth, baseTileHeight);
|
|
|
|
|
|
m_sizeExpanded = QSize(availableWidth, availableHeight);
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < m_tiles.size(); ++i) {
|
|
|
|
|
|
ImageTileWidget* t = m_tiles[i];
|
|
|
|
|
|
bool expanded = anyExpanded && (i == m_expandedIndex);
|
|
|
|
|
|
bool sel = anySelected && (i == m_selectedIndex);
|
|
|
|
|
|
|
|
|
|
|
|
t->setSelected(sel);
|
|
|
|
|
|
t->setExpanded(expanded);
|
|
|
|
|
|
|
|
|
|
|
|
if (expanded) {
|
|
|
|
|
|
// 展开的格子占据整个九宫格空间并支持缩放
|
|
|
|
|
|
t->setMinimumSize(100, 100); // 设置最小尺寸
|
|
|
|
|
|
t->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // 允许最大尺寸
|
|
|
|
|
|
t->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
|
|
|
|
|
|
|
|
|
|
// 重新布局,让展开的格子占据整个九宫格空间
|
|
|
|
|
|
m_layout->removeWidget(t);
|
|
|
|
|
|
m_layout->addWidget(t, 0, 0, m_rows, m_columns);
|
|
|
|
|
|
t->raise(); // 确保展开的格子在最前面
|
|
|
|
|
|
|
|
|
|
|
|
// 强制更新布局,确保展开的瓦片正确缩放
|
|
|
|
|
|
m_layout->invalidate();
|
|
|
|
|
|
updateGeometry();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 恢复正常布局
|
|
|
|
|
|
m_layout->removeWidget(t);
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// 列优先布局:先填满一列再填下一列
|
|
|
|
|
|
int c = i / m_rows;
|
|
|
|
|
|
int r = i % m_rows;
|
|
|
|
|
|
|
|
|
|
|
|
// 奇数排(行号为偶数)图片靠下显示,偶数排(行号为奇数)图片靠上显示
|
|
|
|
|
|
Qt::Alignment imageAlign = (r % 2 == 0) ? Qt::AlignBottom : Qt::AlignTop;
|
|
|
|
|
|
t->setImageAlignment(imageAlign);
|
2025-11-26 22:44:38 +08:00
|
|
|
|
|
2025-11-30 15:38:27 +08:00
|
|
|
|
// 控件在网格中居中显示
|
|
|
|
|
|
m_layout->addWidget(t, r, c, Qt::AlignCenter);
|
|
|
|
|
|
|
|
|
|
|
|
// 设置固定尺寸
|
2025-08-24 23:24:33 +08:00
|
|
|
|
t->setFixedSize(m_sizeNormal);
|
2025-11-30 15:38:27 +08:00
|
|
|
|
t->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
2025-08-24 23:24:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 显示所有格子,让它们都能随窗口放大
|
|
|
|
|
|
for (int i = 0; i < m_tiles.size(); ++i) {
|
|
|
|
|
|
m_tiles[i]->show();
|
|
|
|
|
|
}
|
2025-08-31 21:08:28 +08:00
|
|
|
|
}
|