|
|
|
|
@ -3,22 +3,26 @@
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
|
|
|
|
#include <QAbstractItemModel>
|
|
|
|
|
#include <QColor>
|
|
|
|
|
#include <QIcon>
|
|
|
|
|
#include <QEvent>
|
|
|
|
|
#include <QListWidget>
|
|
|
|
|
#include <QListWidgetItem>
|
|
|
|
|
#include <QPixmap>
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
#include <QObject>
|
|
|
|
|
#include <QPainter>
|
|
|
|
|
#include <QPainterPath>
|
|
|
|
|
#include <QPen>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QStyledItemDelegate>
|
|
|
|
|
|
|
|
|
|
#include "Theme.hpp"
|
|
|
|
|
#include "model/ColorScale.hpp"
|
|
|
|
|
|
|
|
|
|
namespace geopro::app {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
// 颜色块图标边长(像素)。
|
|
|
|
|
constexpr int kSwatch = 12;
|
|
|
|
|
|
|
|
|
|
// 由 localPts 算「位置(质心x)·深(质心y)·尺寸(包络对角)」摘要文本。
|
|
|
|
|
// 异常坐标在剖面距离/深度空间(x=距离米, y=深度米)。
|
|
|
|
|
QString summarize(const geopro::core::Anomaly& a)
|
|
|
|
|
@ -43,15 +47,128 @@ QString summarize(const geopro::core::Anomaly& a)
|
|
|
|
|
.arg(span, 0, 'f', 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// lineColor 字符串("#RRGGBB"/"rgba(...)") → 颜色块 QPixmap。
|
|
|
|
|
QPixmap swatch(const std::string& colorStr)
|
|
|
|
|
// lineColor 字符串 → QColor(兼容 "#RRGGBB" 与 "rgba(...)")。
|
|
|
|
|
QColor barColor(const QString& s)
|
|
|
|
|
{
|
|
|
|
|
const auto c = geopro::core::parseColor(colorStr, geopro::core::AlphaScale::Bit255);
|
|
|
|
|
QPixmap pm(kSwatch, kSwatch);
|
|
|
|
|
pm.fill(QColor(c.r, c.g, c.b));
|
|
|
|
|
return pm;
|
|
|
|
|
const auto c = geopro::core::parseColor(s.toStdString(), geopro::core::AlphaScale::Bit255);
|
|
|
|
|
return QColor(c.r, c.g, c.b);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 右侧眼睛命中区(卡片右端,竖直居中)。
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
const auto& a = anomalies[i];
|
|
|
|
|
const QString name = QString::fromStdString(a.name.empty() ? "异常" : a.name);
|
|
|
|
|
const QString type = QString::fromStdString(a.typeName);
|
|
|
|
|
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);
|
|
|
|
|
auto* item = new QListWidgetItem(name, list);
|
|
|
|
|
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->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
|
|
|
|
|
|