geopro/src/app/TopBar.cpp

365 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 "TopBar.hpp"
#include <QAbstractButton>
#include <QActionGroup>
#include <QColor>
#include <QEvent>
#include <QFont>
#include <QFrame>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QMenuBar>
#include <QPainter>
#include <QPixmap>
#include <QSize>
#include <QVBoxLayout>
#include <QStringList>
#include <QToolButton>
#include <QWidget>
#include "Glyphs.hpp"
#include "Theme.hpp"
namespace geopro::app {
namespace {
// ── 专业图标尺寸(统一放大;菜单栏字号亦同步加大)──
constexpr int kToolIcon = 22; // 工具条右侧图标
constexpr int kWorkspaceIcon = 20; // 工作空间 / 项目图标
// 竖直分隔细线。
QFrame* makeDivider(QWidget* parent)
{
auto* line = new QFrame(parent);
line->setObjectName(QStringLiteral("topDivider"));
line->setFrameShape(QFrame::VLine);
line->setFixedWidth(1);
line->setFixedHeight(24);
return line;
}
// 右侧图标按钮QToolButton + 项目 glyph 图标,随主题着色;悬停底由 #iconBtn QSS 给)。
QWidget* makeIconButton(QWidget* parent, Glyph icon, const QString& tip)
{
auto* btn = new QToolButton(parent);
btn->setObjectName(QStringLiteral("iconBtn"));
setThemedGlyph(btn, icon, kToolIcon);
btn->setIconSize(QSize(kToolIcon, kToolIcon));
btn->setToolTip(tip);
btn->setCursor(Qt::PointingHandCursor);
btn->setAutoRaise(true);
return btn;
}
// 圆形头像图标:强调色填充 + 白色缩写。2x 绘制保证高 DPI 清晰。
QPixmap renderAvatar(const QString& initials, int px, const QColor& bg, const QColor& fg)
{
constexpr int kScale = 2;
const int s = px * kScale;
QPixmap pm(s, s);
pm.fill(Qt::transparent);
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing, true);
p.setPen(Qt::NoPen);
p.setBrush(bg);
p.drawEllipse(0, 0, s, s);
QFont f = p.font();
f.setPixelSize(static_cast<int>(s * 0.4));
f.setBold(true);
p.setFont(f);
p.setPen(fg);
p.drawText(QRect(0, 0, s, s), Qt::AlignCenter, initials);
p.end();
pm.setDevicePixelRatio(kScale);
return pm;
}
// ── 四个菜单(结构对齐需求;叶子项当前为静态占位,后续接真实页面)──
QMenu* buildViewMenu(QWidget* p)
{
auto* m = new QMenu(QStringLiteral("视图"), p);
m->addAction(QStringLiteral("分析视图"));
m->addAction(QStringLiteral("大屏视图"));
return m;
}
QMenu* buildProjectMenu(QWidget* p)
{
auto* m = new QMenu(QStringLiteral("项目管理"), p);
m->addAction(QStringLiteral("数据视图"));
auto* cfg = m->addMenu(QStringLiteral("项目配置"));
cfg->addAction(QStringLiteral("基本信息"));
cfg->addAction(QStringLiteral("项目结构"));
cfg->addAction(QStringLiteral("视图配置"));
m->addAction(QStringLiteral("数据管理"));
auto* biz = m->addMenu(QStringLiteral("业务管理"));
biz->addAction(QStringLiteral("异常管理"));
biz->addAction(QStringLiteral("异常体管理"));
auto* mon = m->addMenu(QStringLiteral("在线监测"));
mon->addAction(QStringLiteral("项目设备"));
mon->addAction(QStringLiteral("在线任务管理"));
auto* doc = m->addMenu(QStringLiteral("项目资料管理"));
doc->addAction(QStringLiteral("项目资料管理"));
doc->addAction(QStringLiteral("报告列表"));
auto* tools = m->addMenu(QStringLiteral("工具组件"));
tools->addAction(QStringLiteral("装置与脚本"));
tools->addAction(QStringLiteral("色阶配置"));
tools->addAction(QStringLiteral("异常类型管理"));
tools->addAction(QStringLiteral("模型管理"));
auto* exp = m->addMenu(QStringLiteral("批量导出"));
exp->addAction(QStringLiteral("文件导出"));
exp->addAction(QStringLiteral("报告导出"));
auto* alarm = m->addMenu(QStringLiteral("告警管理"));
alarm->addAction(QStringLiteral("设备告警"));
alarm->addAction(QStringLiteral("告警查询"));
m->addAction(QStringLiteral("自动任务"));
m->addAction(QStringLiteral("模板管理"));
return m;
}
QMenu* buildToolsMenu(QWidget* p)
{
auto* m = new QMenu(QStringLiteral("业务工具"), p);
m->addAction(QStringLiteral("ERT 思维分析"));
m->addAction(QStringLiteral("电法脚本与装置"));
m->addAction(QStringLiteral("Geo 反演"));
m->addAction(QStringLiteral("三维 GPR 综合分析"));
return m;
}
QMenu* buildDeviceMenu(QWidget* p)
{
auto* m = new QMenu(QStringLiteral("设备"), p);
m->addAction(QStringLiteral("连接设备"));
m->addAction(QStringLiteral("设备管理"));
return m;
}
} // namespace
QWidget* buildMenuBar(QWidget* parent)
{
auto* mb = new QMenuBar(parent);
mb->setObjectName(QStringLiteral("appMenuBar"));
// ElaMenuBar 自绘 Fluent 外观并自动随 ElaTheme 明暗,不再写内联 QSS。
mb->addMenu(buildViewMenu(mb));
mb->addMenu(buildProjectMenu(mb));
mb->addMenu(buildToolsMenu(mb));
mb->addMenu(buildDeviceMenu(mb));
return mb;
}
TopBar::TopBar(QWidget* parent) : QWidget(parent) {
setObjectName(QStringLiteral("appToolBar"));
setFixedHeight(56);
// 字号引用 Theme 排版令牌:工作空间切换器=title(15)、头像/用户名=body·label(13)、
// 角色名=caption(12)。原 11px 角色名上调到 12去掉只差 1px 的糊层级。
// 切换器(ElaToolButton)/图标(ElaIconButton) 自绘 Fluent不再写它们的 QSS。
// 仅保留:工具条底/分隔线、头像(圆形自定义)、用户名/角色。头像白字用 {{text/on-primary}} 令牌。
// 切换器下拉箭头:用生成的高清 chevron PNG 作 menu-indicator替代旧的粗糙文字箭头中性灰双主题可读。
const QString chevron = geopro::app::writeChevronIcon(true, QColor("#7C8493"));
geopro::app::applyTokenizedStyleSheet(
this, QStringLiteral(
"#appToolBar { background:{{bg/header}}; border-bottom:1px solid {{divider}}; }"
"#topDivider { color:{{divider}}; }"
"QToolButton::menu-indicator { image:none; }"
"#wsSwitcher { color:{{text/primary}}; border:none; border-radius:8px; padding:8px 26px 8px 12px;"
" font-size:%6px; font-weight:%4; }"
"#wsSwitcher:hover { background:{{bg/hover}}; }"
"#wsSwitcher::menu-indicator { image:url(%7); width:13px; height:13px;"
" subcontrol-position: right center; subcontrol-origin: padding; right:8px; }"
"QToolButton#iconBtn { border:none; border-radius:8px; padding:8px; }"
"QToolButton#iconBtn:hover { background:{{bg/hover}}; }"
"#userBtn { border:none; border-radius:8px; padding:4px 10px 4px 6px;"
" color:{{text/primary}}; font-size:%3px; }"
"#userBtn:hover { background:{{bg/hover}}; }"
"#userBtn::menu-indicator { image:none; }"
"#avatar { background:{{accent/primary}}; color:{{text/on-primary}}; border-radius:17px; font-weight:%2;"
" font-size:%1px; }"
"#userName { color:{{text/primary}}; font-size:%3px; font-weight:%4; }"
"#userRole { color:{{text/tertiary}}; font-size:%5px; }")
.arg(scaledPx(type::kBody))
.arg(type::kWeightBold)
.arg(scaledPx(type::kLabel))
.arg(type::kWeightSemibold)
.arg(scaledPx(type::kCaption))
.arg(scaledPx(type::kTitle))
.arg(chevron));
auto* lay = new QHBoxLayout(this);
lay->setContentsMargins(14, 0, 14, 0);
lay->setSpacing(0);
// 工作空间切换器QToolButton + 主题化 QSS下拉箭头用高清 chevron menu-indicator数据驱动
wsBtn_ = new QToolButton(this);
wsBtn_->setObjectName(QStringLiteral("wsSwitcher"));
setThemedGlyph(wsBtn_, Glyph::Workspace, kWorkspaceIcon, kGlyphTextGapPad); // 图标→文字6px(§6.7)
wsBtn_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
wsBtn_->setPopupMode(QToolButton::InstantPopup);
wsBtn_->setCursor(Qt::PointingHandCursor);
wsBtn_->setText(QStringLiteral("正在加载工作空间…"));
wsBtn_->setMenu(new QMenu(wsBtn_));
lay->addWidget(wsBtn_);
lay->addSpacing(10);
lay->addWidget(makeDivider(this));
lay->addSpacing(10);
// 项目切换器QToolButton + 主题化 QSS数据驱动
projBtn_ = new QToolButton(this);
projBtn_->setObjectName(QStringLiteral("wsSwitcher"));
setThemedGlyph(projBtn_, Glyph::Folder, kWorkspaceIcon, kGlyphTextGapPad); // 中性主题色 + 图标→文字6px
projBtn_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
projBtn_->setPopupMode(QToolButton::InstantPopup);
projBtn_->setCursor(Qt::PointingHandCursor);
projBtn_->setText(QStringLiteral("正在加载项目…"));
projBtn_->setMenu(new QMenu(projBtn_));
lay->addWidget(projBtn_);
lay->addStretch();
lay->addWidget(makeIconButton(this, Glyph::Help, QStringLiteral("帮助")));
lay->addWidget(makeIconButton(this, Glyph::Bell, QStringLiteral("通知")));
auto* gearBtn = makeIconButton(this, Glyph::Gear, QStringLiteral("设置"));
if (auto* gb = qobject_cast<QAbstractButton*>(gearBtn))
QObject::connect(gb, &QAbstractButton::clicked, this, [this] { emit settingsRequested(); });
lay->addWidget(gearBtn);
lay->addSpacing(10);
lay->addWidget(makeDivider(this));
lay->addSpacing(12);
// 用户区:头像(圆形,竖直居中) + 右侧 姓名(上)/职务(下) 左对齐 + 下拉箭头;整块可点 → 菜单。
// 用普通 QWidget + eventFilterQWidget 按子布局正确撑开QPushButton 装布局会按空文字算尺寸挤成一团)。
userRow_ = new QWidget(this);
userRow_->setObjectName(QStringLiteral("userBtn"));
userRow_->setAttribute(Qt::WA_StyledBackground, true); // 令 QSS 背景(hover)在 QWidget 上生效
userRow_->setCursor(Qt::PointingHandCursor);
userRow_->installEventFilter(this);
auto* uLay = new QHBoxLayout(userRow_);
uLay->setContentsMargins(8, 3, 8, 3);
uLay->setSpacing(10);
auto* avatar = new QLabel(userRow_);
avatar->setPixmap(
renderAvatar(QStringLiteral("ZL"), 34, geopro::app::tokenColor("accent/primary"), Qt::white));
avatar->setFixedSize(34, 34);
avatar->setAttribute(Qt::WA_TransparentForMouseEvents);
uLay->addWidget(avatar, 0, Qt::AlignVCenter);
auto* nameBox = new QWidget(userRow_);
nameBox->setAttribute(Qt::WA_TransparentForMouseEvents);
auto* nameLay = new QVBoxLayout(nameBox);
nameLay->setContentsMargins(0, 0, 0, 0);
nameLay->setSpacing(0);
auto* userName = new QLabel(QStringLiteral("张磊"), nameBox);
userName->setObjectName(QStringLiteral("userName"));
auto* userRole = new QLabel(QStringLiteral("高级工程师"), nameBox);
userRole->setObjectName(QStringLiteral("userRole"));
nameLay->addWidget(userName);
nameLay->addWidget(userRole);
uLay->addWidget(nameBox, 0, Qt::AlignVCenter);
auto* chevronLbl = new QLabel(userRow_);
chevronLbl->setPixmap(QPixmap(geopro::app::writeChevronIcon(true, QColor("#7C8493")))
.scaled(12, 12, Qt::KeepAspectRatio, Qt::SmoothTransformation));
chevronLbl->setAttribute(Qt::WA_TransparentForMouseEvents);
uLay->addWidget(chevronLbl, 0, Qt::AlignVCenter);
// 下拉菜单(加宽):账户 / 个人资料 / 偏好设置 / API 密钥 / 退出登录。
userMenu_ = new QMenu(this);
userMenu_->setMinimumWidth(200);
userMenu_->addAction(QStringLiteral("账户"));
userMenu_->addAction(QStringLiteral("个人资料"));
QObject::connect(userMenu_->addAction(QStringLiteral("偏好设置")), &QAction::triggered, this,
[this] { emit settingsRequested(); });
userMenu_->addAction(QStringLiteral("API 密钥"));
userMenu_->addSeparator();
QObject::connect(userMenu_->addAction(QStringLiteral("退出登录")), &QAction::triggered, this,
[this] { emit logoutRequested(); });
lay->addWidget(userRow_);
}
bool TopBar::eventFilter(QObject* obj, QEvent* event) {
if (obj == userRow_ && event->type() == QEvent::MouseButtonRelease) {
if (userMenu_)
userMenu_->exec(userRow_->mapToGlobal(QPoint(0, userRow_->height() + 2)));
return true;
}
return QWidget::eventFilter(obj, event);
}
void TopBar::setWorkspaces(const std::vector<data::Workspace>& list, const QString& currentId) {
auto* menu = new QMenu(wsBtn_);
auto* header = menu->addAction(QStringLiteral("切换空间"));
header->setEnabled(false);
menu->addSeparator();
auto* group = new QActionGroup(menu);
group->setExclusive(true); // 互斥:只一个勾选,避免“多选”
QString currentName;
for (const auto& w : list) {
const QString id = QString::fromStdString(w.id);
const QString name = QString::fromStdString(w.name);
auto* a = menu->addAction(name);
a->setCheckable(true);
a->setChecked(id == currentId);
group->addAction(a);
if (id == currentId) currentName = name;
QObject::connect(a, &QAction::triggered, this, [this, id, name]() {
wsBtn_->setText(name); // 立即反馈
emit workspaceSwitchRequested(id);
});
}
if (list.empty()) {
auto* none = menu->addAction(QStringLiteral("(暂无空间)"));
none->setEnabled(false);
}
wsBtn_->setMenu(menu);
wsBtn_->setText(currentName.isEmpty() ? QStringLiteral("选择空间") : currentName);
}
void TopBar::setProjects(const std::vector<data::ProjectSummary>& list, const QString& currentId,
bool hasMore) {
auto* menu = new QMenu(projBtn_);
auto* header = menu->addAction(QStringLiteral("切换项目"));
header->setEnabled(false);
menu->addSeparator();
auto* group = new QActionGroup(menu);
group->setExclusive(true);
QString currentName;
for (const auto& p : list) {
const QString id = QString::fromStdString(p.id);
const QString name = QString::fromStdString(p.name);
auto* a = menu->addAction(name);
a->setCheckable(true);
a->setChecked(id == currentId);
group->addAction(a);
if (id == currentId) currentName = name;
QObject::connect(a, &QAction::triggered, this, [this, id, name]() {
projBtn_->setText(name);
emit projectSwitchRequested(id);
});
}
if (list.empty()) {
auto* none = menu->addAction(QStringLiteral("(暂无项目)"));
none->setEnabled(false);
}
if (hasMore) {
menu->addSeparator();
auto* all = menu->addAction(QStringLiteral("全部项目…"));
QObject::connect(all, &QAction::triggered, this, [this]() { emit allProjectsRequested(); });
}
projBtn_->setMenu(menu);
projBtn_->setText(currentName.isEmpty() ? QStringLiteral("选择项目") : currentName);
}
void TopBar::setProjectButtonText(const QString& name) {
projBtn_->setText(name);
}
} // namespace geopro::app