05a685c952
- 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>
249 lines
8.9 KiB
C++
249 lines
8.9 KiB
C++
#include "calculatordesigner.h"
|
|
|
|
#include <QPushButton>
|
|
|
|
#include "expressionevaluator.h"
|
|
#include "ui_widget.h"
|
|
|
|
namespace {
|
|
bool isOperatorChar(QChar ch) {
|
|
return ch == QLatin1Char('+') || ch == QLatin1Char('-') ||
|
|
ch == QLatin1Char('*') || ch == QLatin1Char('/');
|
|
}
|
|
|
|
bool isDigitOrDotChar(QChar ch) {
|
|
return ch.isDigit() || ch == QLatin1Char('.');
|
|
}
|
|
} // namespace
|
|
|
|
CalculatorDesigner::CalculatorDesigner(QWidget* parent)
|
|
: QWidget(parent), ui(new Ui::CalculatorForm) {
|
|
ui->setupUi(this); // 表单里的控件树 + QGridLayout 全部由 Designer XML 建好
|
|
|
|
connect(ui->btn0, &QPushButton::clicked, this, [this] { onDigit(0); });
|
|
connect(ui->btn1, &QPushButton::clicked, this, [this] { onDigit(1); });
|
|
connect(ui->btn2, &QPushButton::clicked, this, [this] { onDigit(2); });
|
|
connect(ui->btn3, &QPushButton::clicked, this, [this] { onDigit(3); });
|
|
connect(ui->btn4, &QPushButton::clicked, this, [this] { onDigit(4); });
|
|
connect(ui->btn5, &QPushButton::clicked, this, [this] { onDigit(5); });
|
|
connect(ui->btn6, &QPushButton::clicked, this, [this] { onDigit(6); });
|
|
connect(ui->btn7, &QPushButton::clicked, this, [this] { onDigit(7); });
|
|
connect(ui->btn8, &QPushButton::clicked, this, [this] { onDigit(8); });
|
|
connect(ui->btn9, &QPushButton::clicked, this, [this] { onDigit(9); });
|
|
connect(ui->btnDot, &QPushButton::clicked, this, [this] { onDot(); });
|
|
|
|
connect(ui->btnPlus, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('+')); });
|
|
connect(ui->btnMinus, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('-')); });
|
|
connect(ui->btnMul, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('*')); });
|
|
connect(ui->btnDiv, &QPushButton::clicked, this, [this] { onOperator(QLatin1Char('/')); });
|
|
|
|
connect(ui->btnLeftParen, &QPushButton::clicked, this, &CalculatorDesigner::onLeftParen);
|
|
connect(ui->btnRightParen, &QPushButton::clicked, this, &CalculatorDesigner::onRightParen);
|
|
|
|
connect(ui->btnEquals, &QPushButton::clicked, this, &CalculatorDesigner::onEquals);
|
|
connect(ui->btnClear, &QPushButton::clicked, this, &CalculatorDesigner::onClear);
|
|
connect(ui->btnBackspace, &QPushButton::clicked, this, &CalculatorDesigner::onBackspace);
|
|
|
|
onClear();
|
|
}
|
|
|
|
CalculatorDesigner::~CalculatorDesigner() { delete ui; }
|
|
|
|
void CalculatorDesigner::onDigit(int digit) {
|
|
if (m_error) onClear();
|
|
if (m_resultShown) {
|
|
m_expression.clear();
|
|
m_openParenCount = 0;
|
|
m_resultShown = false;
|
|
}
|
|
|
|
const QString token = currentOperandToken();
|
|
if (token == QStringLiteral("0")) {
|
|
m_expression.chop(1); // 用新数字替换孤立的前导 0,避免出现 "007"
|
|
} else if (currentOperandDigitCount() >= kMaxOperandLength) {
|
|
return; // 达到单个运算数的长度上限,忽略后续数字
|
|
}
|
|
if (m_expression.size() >= kMaxExpressionLength) return;
|
|
|
|
m_expression.append(QString::number(digit));
|
|
refreshDisplays();
|
|
}
|
|
|
|
void CalculatorDesigner::onDot() {
|
|
if (m_error) onClear();
|
|
if (m_resultShown) {
|
|
m_expression.clear();
|
|
m_openParenCount = 0;
|
|
m_resultShown = false;
|
|
}
|
|
|
|
const QString token = currentOperandToken();
|
|
if (token.contains(QLatin1Char('.'))) return; // 当前运算数已有小数点
|
|
|
|
const int appended = token.isEmpty() ? 2 : 1; // 空token时需要补一个前导 0
|
|
if (m_expression.size() + appended > kMaxExpressionLength) return;
|
|
|
|
if (token.isEmpty()) m_expression.append(QLatin1Char('0'));
|
|
m_expression.append(QLatin1Char('.'));
|
|
refreshDisplays();
|
|
}
|
|
|
|
void CalculatorDesigner::onOperator(QChar op) {
|
|
if (m_error) return;
|
|
if (m_resultShown) {
|
|
m_expression = m_lastResultText;
|
|
m_resultShown = false;
|
|
}
|
|
|
|
if (m_expression.isEmpty()) {
|
|
if (op != QLatin1Char('-')) return; // 表达式开头只允许负号(单目负号)
|
|
} else {
|
|
const QChar last = m_expression.at(m_expression.size() - 1);
|
|
if (last == QLatin1Char('(')) {
|
|
if (op != QLatin1Char('-')) return; // 左括号后只允许负号
|
|
} else if (isOperatorChar(last)) {
|
|
m_expression.chop(1); // 连续按运算符时,用新的运算符替换上一个
|
|
}
|
|
}
|
|
|
|
if (m_expression.size() >= kMaxExpressionLength) return;
|
|
m_expression.append(op);
|
|
refreshDisplays();
|
|
}
|
|
|
|
void CalculatorDesigner::onLeftParen() {
|
|
if (m_error) onClear();
|
|
if (m_resultShown) {
|
|
m_expression.clear();
|
|
m_openParenCount = 0;
|
|
m_resultShown = false;
|
|
}
|
|
|
|
if (!m_expression.isEmpty()) {
|
|
const QChar last = m_expression.at(m_expression.size() - 1);
|
|
if (!isOperatorChar(last) && last != QLatin1Char('(')) {
|
|
return; // 数字或右括号后不支持隐式乘法,忽略
|
|
}
|
|
}
|
|
if (m_expression.size() >= kMaxExpressionLength) return;
|
|
|
|
m_expression.append(QLatin1Char('('));
|
|
++m_openParenCount;
|
|
refreshDisplays();
|
|
}
|
|
|
|
void CalculatorDesigner::onRightParen() {
|
|
if (m_error) return;
|
|
if (m_resultShown) return;
|
|
if (m_openParenCount <= 0 || m_expression.isEmpty()) return;
|
|
|
|
const QChar last = m_expression.at(m_expression.size() - 1);
|
|
if (isOperatorChar(last) || last == QLatin1Char('(')) return; // 括号内需要完整子表达式
|
|
if (m_expression.size() >= kMaxExpressionLength) return;
|
|
|
|
m_expression.append(QLatin1Char(')'));
|
|
--m_openParenCount;
|
|
refreshDisplays();
|
|
}
|
|
|
|
void CalculatorDesigner::onEquals() {
|
|
if (m_error) return;
|
|
if (m_expression.isEmpty()) return;
|
|
|
|
QString toEvaluate = m_expression;
|
|
for (int i = 0; i < m_openParenCount; ++i) toEvaluate.append(QLatin1Char(')')); // 自动补全未闭合括号
|
|
|
|
const ExpressionEvaluator::Result result = ExpressionEvaluator::evaluate(toEvaluate);
|
|
if (!result.ok) {
|
|
showError();
|
|
return;
|
|
}
|
|
|
|
const QString resultText = QString::number(result.value, 'g', ExpressionEvaluator::kResultPrecision);
|
|
m_expression = toEvaluate;
|
|
m_lastResultText = resultText;
|
|
m_openParenCount = 0;
|
|
m_resultShown = true;
|
|
ui->expressionEdit->setPlainText(m_expression + QStringLiteral(" = ") + resultText);
|
|
ui->display->setText(resultText);
|
|
|
|
// 【新增】通知外壳(历史停靠窗):本次求值的表达式与结果。
|
|
emit evaluated(m_expression, resultText);
|
|
}
|
|
|
|
void CalculatorDesigner::onClear() {
|
|
m_expression.clear();
|
|
m_lastResultText.clear();
|
|
m_openParenCount = 0;
|
|
m_resultShown = false;
|
|
m_error = false;
|
|
ui->expressionEdit->clear();
|
|
ui->display->setText(QStringLiteral("0"));
|
|
}
|
|
|
|
void CalculatorDesigner::onBackspace() {
|
|
if (m_error) { onClear(); return; }
|
|
if (m_resultShown) return; // 结果已展示,需先清空或继续输入新表达式
|
|
if (m_expression.isEmpty()) return;
|
|
|
|
const QChar removed = m_expression.at(m_expression.size() - 1);
|
|
m_expression.chop(1);
|
|
if (removed == QLatin1Char('(')) --m_openParenCount;
|
|
else if (removed == QLatin1Char(')')) ++m_openParenCount;
|
|
refreshDisplays();
|
|
}
|
|
|
|
QString CalculatorDesigner::displayText() const { return ui->display->text(); }
|
|
|
|
QString CalculatorDesigner::expressionText() const { return ui->expressionEdit->toPlainText(); }
|
|
|
|
// 【新增】把一条历史表达式回填到编辑区:重置状态机并把表达式原样载入,
|
|
// 未闭合括号数按字符重新统计。调用后用户可直接按 = 重新求值,或继续编辑。
|
|
void CalculatorDesigner::loadExpression(const QString& expr) {
|
|
if (m_error) onClear();
|
|
m_expression = expr;
|
|
m_openParenCount = 0;
|
|
for (const QChar ch : m_expression) {
|
|
if (ch == QLatin1Char('(')) ++m_openParenCount;
|
|
else if (ch == QLatin1Char(')')) --m_openParenCount;
|
|
}
|
|
if (m_openParenCount < 0) m_openParenCount = 0;
|
|
m_resultShown = false;
|
|
m_lastResultText.clear();
|
|
refreshDisplays();
|
|
}
|
|
|
|
QString CalculatorDesigner::currentOperandToken() const {
|
|
int start = m_expression.size();
|
|
while (start > 0 && isDigitOrDotChar(m_expression.at(start - 1))) --start;
|
|
return m_expression.mid(start);
|
|
}
|
|
|
|
int CalculatorDesigner::currentOperandDigitCount() const {
|
|
const QString token = currentOperandToken();
|
|
int count = 0;
|
|
for (const QChar ch : token) {
|
|
if (ch.isDigit()) ++count;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
QString CalculatorDesigner::lastNumberToken() const {
|
|
int end = m_expression.size();
|
|
while (end > 0 && !isDigitOrDotChar(m_expression.at(end - 1))) --end; // 跳过末尾的运算符/括号
|
|
int start = end;
|
|
while (start > 0 && isDigitOrDotChar(m_expression.at(start - 1))) --start;
|
|
return m_expression.mid(start, end - start);
|
|
}
|
|
|
|
void CalculatorDesigner::refreshDisplays() {
|
|
ui->expressionEdit->setPlainText(m_expression);
|
|
const QString token = lastNumberToken();
|
|
ui->display->setText(token.isEmpty() ? QStringLiteral("0") : token);
|
|
}
|
|
|
|
void CalculatorDesigner::showError() {
|
|
ui->display->setText(QStringLiteral("Error"));
|
|
m_error = true;
|
|
}
|