feat(panels): 异常列表卡片化(色条+类型标签+显隐眼睛,真实数据)(规范§6.3)
This commit is contained in:
parent
b26dcc1ca7
commit
8f31f043df
|
|
@ -563,6 +563,7 @@ void buildWorkbench(QMainWindow& window, geopro::data::LocalSampleRepository& re
|
||||||
// 右上 dock:异常列表 / 对象属性 合并为带 Tab 表头的面板(对齐原型上半)。
|
// 右上 dock:异常列表 / 对象属性 合并为带 Tab 表头的面板(对齐原型上半)。
|
||||||
auto* anomalyList = new QListWidget();
|
auto* anomalyList = new QListWidget();
|
||||||
applyListSelection(anomalyList);
|
applyListSelection(anomalyList);
|
||||||
|
geopro::app::applyAnomalyCardDelegate(anomalyList);
|
||||||
auto* objAttrLabel = new QLabel(QStringLiteral("(选中对象后显示其属性)"));
|
auto* objAttrLabel = new QLabel(QStringLiteral("(选中对象后显示其属性)"));
|
||||||
objAttrLabel->setWordWrap(true);
|
objAttrLabel->setWordWrap(true);
|
||||||
objAttrLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
objAttrLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,26 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QIcon>
|
#include <QEvent>
|
||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
#include <QPixmap>
|
#include <QMouseEvent>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPainterPath>
|
||||||
|
#include <QPen>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QStyledItemDelegate>
|
||||||
|
|
||||||
|
#include "Theme.hpp"
|
||||||
#include "model/ColorScale.hpp"
|
#include "model/ColorScale.hpp"
|
||||||
|
|
||||||
namespace geopro::app {
|
namespace geopro::app {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// 颜色块图标边长(像素)。
|
|
||||||
constexpr int kSwatch = 12;
|
|
||||||
|
|
||||||
// 由 localPts 算「位置(质心x)·深(质心y)·尺寸(包络对角)」摘要文本。
|
// 由 localPts 算「位置(质心x)·深(质心y)·尺寸(包络对角)」摘要文本。
|
||||||
// 异常坐标在剖面距离/深度空间(x=距离米, y=深度米)。
|
// 异常坐标在剖面距离/深度空间(x=距离米, y=深度米)。
|
||||||
QString summarize(const geopro::core::Anomaly& a)
|
QString summarize(const geopro::core::Anomaly& a)
|
||||||
|
|
@ -43,15 +47,128 @@ QString summarize(const geopro::core::Anomaly& a)
|
||||||
.arg(span, 0, 'f', 0);
|
.arg(span, 0, 'f', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lineColor 字符串("#RRGGBB"/"rgba(...)") → 颜色块 QPixmap。
|
// lineColor 字符串 → QColor(兼容 "#RRGGBB" 与 "rgba(...)")。
|
||||||
QPixmap swatch(const std::string& colorStr)
|
QColor barColor(const QString& s)
|
||||||
{
|
{
|
||||||
const auto c = geopro::core::parseColor(colorStr, geopro::core::AlphaScale::Bit255);
|
const auto c = geopro::core::parseColor(s.toStdString(), geopro::core::AlphaScale::Bit255);
|
||||||
QPixmap pm(kSwatch, kSwatch);
|
return QColor(c.r, c.g, c.b);
|
||||||
pm.fill(QColor(c.r, c.g, c.b));
|
|
||||||
return pm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右侧眼睛命中区(卡片右端,竖直居中)。
|
||||||
|
QRect anomalyEyeRect(const QRect& itemRect)
|
||||||
|
{
|
||||||
|
const QRect r = itemRect.adjusted(4, 2, -4, -2);
|
||||||
|
const int sz = 22;
|
||||||
|
return QRect(r.right() - sz - 8, r.center().y() - sz / 2, sz, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnomalyCardDelegate : public QStyledItemDelegate {
|
||||||
|
public:
|
||||||
|
using QStyledItemDelegate::QStyledItemDelegate;
|
||||||
|
|
||||||
|
QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override
|
||||||
|
{
|
||||||
|
return QSize(0, 58);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool editorEvent(QEvent* e, QAbstractItemModel* model, const QStyleOptionViewItem& opt,
|
||||||
|
const QModelIndex& idx) override
|
||||||
|
{
|
||||||
|
if (e->type() == QEvent::MouseButtonRelease) {
|
||||||
|
auto* me = static_cast<QMouseEvent*>(e);
|
||||||
|
if (anomalyEyeRect(opt.rect).contains(me->position().toPoint())) {
|
||||||
|
const auto cur = static_cast<Qt::CheckState>(idx.data(Qt::CheckStateRole).toInt());
|
||||||
|
model->setData(idx, cur == Qt::Checked ? Qt::Unchecked : Qt::Checked,
|
||||||
|
Qt::CheckStateRole);
|
||||||
|
return true; // 吃掉点击:只切显隐,不改选中
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return QStyledItemDelegate::editorEvent(e, model, opt, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void paint(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& idx) const override
|
||||||
|
{
|
||||||
|
p->save();
|
||||||
|
p->setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
|
||||||
|
const QRect r = opt.rect.adjusted(4, 3, -4, -3);
|
||||||
|
const bool selected = opt.state & QStyle::State_Selected;
|
||||||
|
const bool hover = opt.state & QStyle::State_MouseOver;
|
||||||
|
|
||||||
|
// 卡底(hover/选中高亮)
|
||||||
|
if (selected || hover) {
|
||||||
|
QPainterPath path; path.addRoundedRect(r, 6, 6);
|
||||||
|
p->fillPath(path, geopro::app::tokenColor(selected ? "bg/selected" : "bg/hover"));
|
||||||
|
}
|
||||||
|
// 左 3px 状态色竖条(取异常自身 lineColor)
|
||||||
|
p->fillRect(QRect(r.left(), r.top() + 4, 3, r.height() - 8),
|
||||||
|
barColor(idx.data(kAnomalyColorRole).toString()));
|
||||||
|
|
||||||
|
const QString name = idx.data(Qt::DisplayRole).toString();
|
||||||
|
const QString type = idx.data(kAnomalyTypeRole).toString();
|
||||||
|
const QString summary = idx.data(kAnomalySummaryRole).toString();
|
||||||
|
|
||||||
|
const int left = r.left() + 14;
|
||||||
|
const int right = anomalyEyeRect(opt.rect).left() - 8; // 给眼睛留位
|
||||||
|
const int rowW = right - left;
|
||||||
|
|
||||||
|
// 第一行:名称(加粗)
|
||||||
|
QFont nf = opt.font; nf.setPixelSize(geopro::app::scaledPx(13)); nf.setWeight(QFont::DemiBold);
|
||||||
|
p->setFont(nf);
|
||||||
|
p->setPen(geopro::app::tokenColor("text/primary"));
|
||||||
|
const QRect nameR(left, r.top() + 8, rowW, 20);
|
||||||
|
p->drawText(nameR, Qt::AlignLeft | Qt::AlignVCenter,
|
||||||
|
p->fontMetrics().elidedText(name, Qt::ElideRight, rowW));
|
||||||
|
|
||||||
|
// 第二行:类型胶囊 + 摘要
|
||||||
|
int x = left;
|
||||||
|
const int cy = r.top() + 38;
|
||||||
|
if (!type.isEmpty()) {
|
||||||
|
QFont pf = opt.font; pf.setPixelSize(geopro::app::scaledPx(11));
|
||||||
|
p->setFont(pf);
|
||||||
|
const QFontMetrics fm(pf);
|
||||||
|
const int tw = fm.horizontalAdvance(type);
|
||||||
|
const int ph = fm.height() + 2;
|
||||||
|
const QRect pill(x, cy - ph / 2, tw + 12, ph);
|
||||||
|
QPainterPath pp; pp.addRoundedRect(pill, ph / 2.0, ph / 2.0);
|
||||||
|
p->fillPath(pp, geopro::app::tokenColor("bg/hover"));
|
||||||
|
p->setPen(geopro::app::tokenColor("text/secondary"));
|
||||||
|
p->drawText(pill, Qt::AlignCenter, type);
|
||||||
|
x = pill.right() + 8;
|
||||||
|
}
|
||||||
|
if (!summary.isEmpty()) {
|
||||||
|
QFont sf = opt.font; sf.setPixelSize(geopro::app::scaledPx(11));
|
||||||
|
p->setFont(sf);
|
||||||
|
p->setPen(geopro::app::tokenColor("text/secondary"));
|
||||||
|
const QRect sumR(x, cy - 10, right - x, 20);
|
||||||
|
p->drawText(sumR, Qt::AlignLeft | Qt::AlignVCenter,
|
||||||
|
p->fontMetrics().elidedText(summary, Qt::ElideRight, sumR.width()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧眼睛(显隐):可见=次要色睁眼;隐藏=禁用色 + 斜杠
|
||||||
|
const bool visible =
|
||||||
|
static_cast<Qt::CheckState>(idx.data(Qt::CheckStateRole).toInt()) == Qt::Checked;
|
||||||
|
const QColor eyeCol = geopro::app::tokenColor(visible ? "text/secondary" : "text/disabled");
|
||||||
|
const QRectF eb = anomalyEyeRect(opt.rect);
|
||||||
|
const QPointF c = eb.center();
|
||||||
|
const double w = eb.width() * 0.42, h = eb.height() * 0.24;
|
||||||
|
p->setPen(QPen(eyeCol, 1.4));
|
||||||
|
p->setBrush(Qt::NoBrush);
|
||||||
|
QPainterPath eye;
|
||||||
|
eye.moveTo(c.x() - w, c.y());
|
||||||
|
eye.quadTo(c.x(), c.y() - h * 2.0, c.x() + w, c.y());
|
||||||
|
eye.quadTo(c.x(), c.y() + h * 2.0, c.x() - w, c.y());
|
||||||
|
p->drawPath(eye);
|
||||||
|
p->setBrush(eyeCol);
|
||||||
|
p->drawEllipse(c, h * 0.95, h * 0.95);
|
||||||
|
p->setBrush(Qt::NoBrush);
|
||||||
|
if (!visible)
|
||||||
|
p->drawLine(QPointF(c.x() - w, c.y() + h * 1.6), QPointF(c.x() + w, c.y() - h * 1.6));
|
||||||
|
|
||||||
|
p->restore();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void populateAnomalyList(QListWidget* list, const std::vector<geopro::core::Anomaly>& anomalies)
|
void populateAnomalyList(QListWidget* list, const std::vector<geopro::core::Anomaly>& anomalies)
|
||||||
|
|
@ -61,16 +178,24 @@ void populateAnomalyList(QListWidget* list, const std::vector<geopro::core::Anom
|
||||||
for (std::size_t i = 0; i < anomalies.size(); ++i) {
|
for (std::size_t i = 0; i < anomalies.size(); ++i) {
|
||||||
const auto& a = anomalies[i];
|
const auto& a = anomalies[i];
|
||||||
const QString name = QString::fromStdString(a.name.empty() ? "异常" : a.name);
|
const QString name = QString::fromStdString(a.name.empty() ? "异常" : a.name);
|
||||||
const QString type = QString::fromStdString(a.typeName);
|
auto* item = new QListWidgetItem(name, list);
|
||||||
QString text = name;
|
|
||||||
if (!type.isEmpty()) text += QStringLiteral("(%1)").arg(type);
|
|
||||||
text += QStringLiteral("\n%1").arg(summarize(a));
|
|
||||||
|
|
||||||
auto* item = new QListWidgetItem(QIcon(swatch(a.lineColor)), text, list);
|
|
||||||
item->setData(kAnomalyIndexRole, static_cast<int>(i));
|
item->setData(kAnomalyIndexRole, static_cast<int>(i));
|
||||||
|
item->setData(kAnomalyColorRole, QString::fromStdString(a.lineColor));
|
||||||
|
item->setData(kAnomalyTypeRole, QString::fromStdString(a.typeName));
|
||||||
|
item->setData(kAnomalySummaryRole, summarize(a));
|
||||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||||
item->setCheckState(Qt::Checked); // 默认显示
|
item->setCheckState(Qt::Checked); // 默认显示
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void applyAnomalyCardDelegate(QListWidget* list)
|
||||||
|
{
|
||||||
|
if (!list) return;
|
||||||
|
list->setItemDelegate(new AnomalyCardDelegate(list));
|
||||||
|
list->setMouseTracking(true);
|
||||||
|
list->setSpacing(0);
|
||||||
|
QObject::connect(&ThemeManager::instance(), &ThemeManager::changed, list,
|
||||||
|
[list]() { list->viewport()->update(); });
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace geopro::app
|
} // namespace geopro::app
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,14 @@ namespace geopro::app {
|
||||||
// 异常索引存于条目的 Qt::UserRole(= 在原异常 vector 中的下标,用于显隐映射)。
|
// 异常索引存于条目的 Qt::UserRole(= 在原异常 vector 中的下标,用于显隐映射)。
|
||||||
constexpr int kAnomalyIndexRole = 0x0100; // Qt::UserRole
|
constexpr int kAnomalyIndexRole = 0x0100; // Qt::UserRole
|
||||||
|
|
||||||
|
// 卡片委托读取的结构化角色(避免把数据塞进显示文本)。
|
||||||
|
constexpr int kAnomalyColorRole = 0x0101; // lineColor 字符串
|
||||||
|
constexpr int kAnomalyTypeRole = 0x0102; // typeName
|
||||||
|
constexpr int kAnomalySummaryRole = 0x0103; // 位置·深·尺寸 摘要
|
||||||
|
|
||||||
|
// 给异常列表套用卡片委托(左色条+名称+类型标签+摘要+右侧显隐眼睛,规范§6.3)。
|
||||||
|
void applyAnomalyCardDelegate(QListWidget* list);
|
||||||
|
|
||||||
// 用异常填充 QListWidget(对齐原型右上「异常列表」):每条目 = 颜色块图标 + 名称 +
|
// 用异常填充 QListWidget(对齐原型右上「异常列表」):每条目 = 颜色块图标 + 名称 +
|
||||||
// 派生「位置 Xm · 深 Ym · 尺寸 Zm」(由 location.coordinate 质心/包络算)。
|
// 派生「位置 Xm · 深 Ym · 尺寸 Zm」(由 location.coordinate 质心/包络算)。
|
||||||
// 条目可勾选:勾=显示(默认全勾);勾选状态变化由调用方连接驱动该异常 actor 显隐。
|
// 条目可勾选:勾=显示(默认全勾);勾选状态变化由调用方连接驱动该异常 actor 显隐。
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue