Files
张宗平 05a685c952 Day3 教学示例:增强版计算器接入 + QMainWindow/QSS 两版参考实现
- calculator_designer_ext(Day2 增强版:表达式求值 + 括号,递归下降解析器)接入 cmake
- calculator_mainwindow(Day3 上午):增强版计算器装进 QMainWindow,
  菜单/状态栏/历史停靠窗,纯代码 + 显式 connect,与 mainwindow_showcase 形成坑卡对照
- calculator_mainwindow_qss(Day3 下午):上午版 + QSS 双主题美化(按键分色/伪状态/深浅切换)
- 验证 mainwindow_showcase(QMainWindow 六大组件综合演示,.ui + .qrc)
- OUTLINE_4DAY.md / ASSIGNMENTS.md Day3 衔接增强版起点 + 参考实现指引
- 补提交 Day2 calculator/ 与 calculator_designer/ 参考实现(CMakeLists 已引用)
- 全量构建 28/28,4 目标离屏自测退出码 0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 22:59:05 +08:00

213 lines
9.5 KiB
C++
Raw Permalink 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.
// ============================================================================
// mainwindow.cpp —— Day3 上午版实现
// 详见 mainwindow.h 顶部注释。本文件只做外壳组装,不含任何计算逻辑。
// ============================================================================
#include "mainwindow.h"
#include "calculatordesigner.h"
#include <QAction>
#include <QDebug>
#include <QDockWidget>
#include <QKeySequence>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMenu>
#include <QMenuBar>
#include <QMessageBox>
#include <QStatusBar>
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
setWindowTitle(QStringLiteral("计算器(QMainWindow 版)"));
// ★ 中心部件必须显式安放(Day3 上午坑卡:直接往 QMainWindow 上 addWidget
// 布局行为不可预期)。CalculatorDesigner 是 QWidget,原样塞进去即可。
m_calc = new CalculatorDesigner(this);
setCentralWidget(m_calc);
buildMenu();
buildDock();
buildStatusBar();
// 计算器求值成功 → 历史停靠窗追加一条(Day2 信号槽的复用:给已有部件
// 加 evaluated 信号,外壳订阅它)。
connect(m_calc, &CalculatorDesigner::evaluated, this, &MainWindow::onEvaluated);
}
MainWindow::~MainWindow() = default;
// ---- 菜单栏(纯代码 + 显式 connect,对照 mainwindow_showcase 的 .ui 自动连接)----
void MainWindow::buildMenu() {
QMenu* fileMenu = menuBar()->addMenu(QStringLiteral("文件(&F)"));
m_actQuit = fileMenu->addAction(QStringLiteral("退出(&Q)"));
m_actQuit->setShortcut(QKeySequence(QStringLiteral("Ctrl+Q")));
connect(m_actQuit, &QAction::triggered, this, &QWidget::close);
QMenu* editMenu = menuBar()->addMenu(QStringLiteral("编辑(&E)"));
m_actClearHistory = editMenu->addAction(QStringLiteral("清空历史(&C)"));
connect(m_actClearHistory, &QAction::triggered, this, &MainWindow::onClearHistory);
QMenu* viewMenu = menuBar()->addMenu(QStringLiteral("视图(&V)"));
m_actToggleHistory = viewMenu->addAction(QStringLiteral("显示历史(&H)"));
m_actToggleHistory->setCheckable(true);
m_actToggleHistory->setChecked(true);
connect(m_actToggleHistory, &QAction::toggled, this, &MainWindow::onToggleHistoryVisible);
QMenu* helpMenu = menuBar()->addMenu(QStringLiteral("帮助(&H)"));
m_actAbout = helpMenu->addAction(QStringLiteral("关于(&A)"));
// ★ 显式 connect:忘了这一行,菜单显示正常、点了没反应(Day3 上午坑卡)。
connect(m_actAbout, &QAction::triggered, this, &MainWindow::onAbout);
}
// ---- 历史停靠窗 ----
void MainWindow::buildDock() {
m_history = new QListWidget(this);
m_history->setAlternatingRowColors(true);
m_dock = new QDockWidget(QStringLiteral("历史"), this);
m_dock->setObjectName(QStringLiteral("historyDock")); // 供 saveState/restoreState
m_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea);
m_dock->setWidget(m_history);
addDockWidget(Qt::RightDockWidgetArea, m_dock);
// 双击历史条目 → 回填表达式(QListWidget 信号槽)
connect(m_history, &QListWidget::itemDoubleClicked, this, &MainWindow::onHistoryDoubleClicked);
}
void MainWindow::buildStatusBar() {
statusBar()->showMessage(QStringLiteral("就绪"));
}
void MainWindow::refreshStatus(const QString& msg) {
statusBar()->showMessage(msg, 3000); // 临时提示,3 秒后恢复
}
// ---- 槽 ----
void MainWindow::onAbout() {
// 标准对话框:QMessageBox::about 是模态的,exec() 内部阻塞等用户关闭。
QMessageBox::about(this, QStringLiteral("关于"),
QStringLiteral(
"<b>计算器(QMainWindow 版)</b><br><br>"
"Day3 上午教学参考实现:把 Day2 增强版计算器(表达式求值 + 括号)"
"装进 QMainWindow,演示菜单栏 / 状态栏 / 历史停靠窗。<br><br>"
"对应教案 OUTLINE_4DAY.md Day3 上午。"));
}
void MainWindow::onClearHistory() {
m_history->clear();
refreshStatus(QStringLiteral("已清空历史"));
}
void MainWindow::onToggleHistoryVisible(bool checked) {
m_dock->setVisible(checked);
}
void MainWindow::onEvaluated(const QString& expr, const QString& result) {
m_history->addItem(expr + QStringLiteral(" = ") + result);
refreshStatus(QStringLiteral("结果: ") + result);
}
void MainWindow::onHistoryDoubleClicked(QListWidgetItem* item) {
// 条目文本形如 "expr = result",取最后一个 " = " 左侧作为表达式回填。
// 用 lastIndexOf 而非 indexOfresult 文本里一般不含 " = ",但用 lastIndexOf
// 更稳妥(避免表达式里万一出现 " = ",实际上表达式不会含 =)。
const QString text = item->text();
const int idx = text.lastIndexOf(QStringLiteral(" = "));
if (idx < 0) return;
m_calc->loadExpression(text.left(idx));
refreshStatus(QStringLiteral("已回填表达式"));
}
// ------------------------------------------------------------ 自测 ----
bool MainWindow::runSelfTest() {
auto expect = [](const char* label, bool cond) -> bool {
if (!cond) { qCritical() << "自测失败:" << label; return false; }
qInfo() << "自测通过:" << label; return true;
};
auto expectEq = [](const char* label, const QString& actual, const QString& expected) -> bool {
if (actual != expected) {
qCritical() << "自测失败:" << label << "实际=" << actual << "预期=" << expected;
return false;
}
qInfo() << "自测通过:" << label << "=>" << actual;
return true;
};
// —— Day2 计算 9 组用例(验证升级外壳后计算行为不变)——
m_calc->onClear();
m_calc->onDigit(7); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(3); m_calc->onEquals();
if (!expectEq("7 + 3 =", m_calc->displayText(), QStringLiteral("10"))) return false;
if (!expect("历史追加第 1 条", m_history->count() == 1)) return false;
if (!expectEq("历史条目文本", m_history->item(0)->text(), QStringLiteral("7+3 = 10"))) return false;
m_calc->onClear();
m_calc->onDigit(6); m_calc->onOperator(QLatin1Char('/')); m_calc->onDigit(0); m_calc->onEquals();
if (!expectEq("6 ÷ 0 =", m_calc->displayText(), QStringLiteral("Error"))) return false;
if (!expect("除零 Error 不进历史", m_history->count() == 1)) return false;
m_calc->onClear();
m_calc->onDigit(4); m_calc->onDot(); m_calc->onDigit(5);
m_calc->onOperator(QLatin1Char('+'));
m_calc->onDigit(0); m_calc->onDot(); m_calc->onDigit(5);
m_calc->onEquals();
if (!expectEq("4.5 + 0.5 =", m_calc->displayText(), QStringLiteral("5"))) return false;
m_calc->onClear();
m_calc->onDigit(1); m_calc->onDigit(2); m_calc->onBackspace();
if (!expectEq("12 退格", m_calc->displayText(), QStringLiteral("1"))) return false;
m_calc->onClear();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('+'));
m_calc->onDigit(3); m_calc->onOperator(QLatin1Char('+'));
m_calc->onDigit(4); m_calc->onEquals();
if (!expectEq("2 + 3 + 4 =", m_calc->displayText(), QStringLiteral("9"))) return false;
m_calc->onClear();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('+'));
m_calc->onDigit(3); m_calc->onOperator(QLatin1Char('*'));
m_calc->onDigit(4); m_calc->onEquals();
if (!expectEq("2 + 3 × 4 =", m_calc->displayText(), QStringLiteral("14"))) return false;
m_calc->onClear();
m_calc->onLeftParen();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(3);
m_calc->onRightParen();
m_calc->onOperator(QLatin1Char('*'));
m_calc->onDigit(4); m_calc->onEquals();
if (!expectEq("(2 + 3) × 4 =", m_calc->displayText(), QStringLiteral("20"))) return false;
m_calc->onClear();
m_calc->onDigit(1); m_calc->onOperator(QLatin1Char('+'));
m_calc->onLeftParen();
m_calc->onDigit(2); m_calc->onOperator(QLatin1Char('*'));
m_calc->onLeftParen();
m_calc->onDigit(3); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(4);
m_calc->onRightParen();
m_calc->onRightParen();
m_calc->onEquals();
if (!expectEq("1 + (2 × (3 + 4)) =", m_calc->displayText(), QStringLiteral("15"))) return false;
m_calc->onClear();
m_calc->onLeftParen();
m_calc->onDigit(1); m_calc->onOperator(QLatin1Char('+')); m_calc->onDigit(2);
m_calc->onEquals();
if (!expectEq("(1 + 2 = 自动补全", m_calc->displayText(), QStringLiteral("3"))) return false;
// 9 组用例里:第 4 组(12 退格)无 onEquals、第 2 组(6÷0Error 不 emit
// 其余 7 次 onEquals 各进历史一条。
if (!expect("历史累计 7 条", m_history->count() == 7)) return false;
// —— Day3 新增:历史双击回填 ——
QListWidgetItem* last = m_history->item(m_history->count() - 1);
if (!expectEq("最后一条历史", last->text(), QStringLiteral("(1+2) = 3"))) return false;
onHistoryDoubleClicked(last);
if (!expectEq("双击回填表达式区", m_calc->expressionText(), QStringLiteral("(1+2)"))) return false;
// —— Day3 新增:清空历史 ——
onClearHistory();
if (!expect("清空历史后 count==0", m_history->count() == 0)) return false;
// —— Day3 新增:关于 action 已创建并显式连接 ——
// QMessageBox::about 是模态弹窗,离屏下不真正触发,GUI 弹窗验证靠现场演示)
if (!expect("关于 action 已创建", m_actAbout != nullptr)) return false;
return true;
}