First Commit

This commit is contained in:
xiaolinquan committed 2026-11-06 01:04:35 +08:00
commit 3796ec5cd2
146 files changed
+4825

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
cmake_minimum_required(VERSION 3.16)
project(ClassroomTimer VERSION 1.0 LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
if(QT_VERSION_MAJOR EQUAL 5 AND Qt5Core_VERSION VERSION_LESS 5.14.2)
message(FATAL_ERROR "Qt 5.14.2 or newer is required.")
endif()
if(QT_VERSION_MAJOR EQUAL 6)
set(CMAKE_CXX_STANDARD 17)
endif()
if(MSVC)
add_compile_options(/utf-8)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
add_executable(ClassroomTimer WIN32 main.cpp appearance.h mainwindow.cpp mainwindow.h countdown.cpp countdown.h mainwindow.ui)
target_link_libraries(ClassroomTimer PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
option(BUILD_TEACHING_TESTS "Build teaching project tests" OFF)
if(BUILD_TEACHING_TESTS)
enable_testing()
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Test)
add_executable(test_ClassroomTimer tests/test_ClassroomTimer.cpp mainwindow.cpp mainwindow.h
countdown.cpp countdown.h mainwindow.ui)
target_include_directories(test_ClassroomTimer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_ClassroomTimer PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Test)
add_test(NAME ClassroomTimer COMMAND test_ClassroomTimer -platform offscreen)
endif()
@@ -0,0 +1,10 @@
QT += widgets
CONFIG += c++14
greaterThan(QT_MAJOR_VERSION, 5): CONFIG += c++17
TEMPLATE = app
TARGET = ClassroomTimer
SOURCES += main.cpp mainwindow.cpp countdown.cpp
HEADERS += mainwindow.h countdown.h
FORMS += mainwindow.ui
msvc: QMAKE_CXXFLAGS += /utf-8
+65
View File
@@ -0,0 +1,65 @@
# 项目二:课堂计时器
适合第3—4章讲解信号槽、定时器、状态变化和数值输入。可用于限时练习、分组讨论与课堂展示。
## 功能与操作
时长范围为 0 到 23:59:59,但零时长不能开始。默认时长为 5 分钟。
- 开始:从设置的时长开始计时。
- 暂停:保存此刻的剩余时间。
- 继续:从保存的剩余时间接着计时。
- 复位:停止本轮计时,并恢复到设置的完整时长。
- 重新开始:到时后开始新的一轮。
- 全屏显示:便于投影;按 Esc 退出全屏。
- 到时提示:显示“时间到”,并可播放一次系统提示音。能否听到提示音取决于系统声音设置。
运行中和暂停时,时长输入框均被锁定;先复位,再修改时长。
## 课堂任务
先连接开始按钮和 QTimer,让学生观察 timeout 信号重复到达。随后故意安排一次较慢的界面处理,讨论“每次 timeout 减 100 毫秒”为什么可能不准确。
QTimer 在这个项目中每隔约 100 毫秒请求刷新显示。实际剩余时间由 QElapsedTimer 提供的经过时间计算,不依赖刷新次数。即使某次刷新较晚,下一次仍按实际经过的时间计算。
讲解模型时可先使用确定的数值:总时长 10 秒,在第 2.5 秒暂停,还剩 7.5 秒;暂停 1 分钟后继续,剩余时间仍从 7.5 秒开始。
## 核心数据
Countdown 保存四项信息:完整时长、当前计时片段开始时的剩余量、该片段的开始时刻,以及状态。
运行时:
剩余毫秒 = max(0, 片段开始时的剩余毫秒 − 本片段已过去的毫秒)。
暂停时先算出剩余毫秒并保存,再切换到 Paused。继续时只记录新的片段开始时刻,不恢复完整时长。
界面显示秒数采用向上取整。还剩 1 毫秒时显示 1 秒,真正到时才显示 0,避免“已经显示零但还没结束”的现象。
## 状态与操作
| 当前状态 | 开始按钮 | 暂停按钮 | 时长输入 |
|---|---|---|---|
| Ready | 开始,零时长除外 | 不可用 | 可修改 |
| Running | 不可用 | 暂停 | 锁定 |
| Paused | 继续 | 不可用 | 锁定 |
| Finished | 重新开始 | 不可用 | 可修改 |
refreshDisplay 根据同一个状态统一设置按钮,避免在多个槽函数中分别修改后产生矛盾。
## 学生练习
1. 加入 1、3、5 分钟快捷设置按钮,并遵守运行中不能改变时长的规则。
2. 最后 10 秒改变数字颜色,到时和复位后恢复正确颜色。
3. 增加本轮标题,例如“独立练习”。标题变化不应重新开始计时。
## 验收用例
- 设置 1 秒并开始:到时显示 00:00:00,暂停按钮不可用。
- 运行时连续触发开始操作:不会重新计算起点。
- 暂停后等待:剩余时间保持不变;继续后接着减少。
- 运行或暂停时点击复位:回到原设置时长,并允许修改。
- 设置零时长:不能开始。
- 模型两次更新之间跨过截止时间:剩余量直接归零,不出现负数。
自动化测试通过传入固定的毫秒数检查计时规则;另有真实按钮测试确认计时模型与界面的连接。
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <QApplication>
#include <QFont>
#include <QStyleFactory>
inline void applyAppearance(QApplication &app)
{
app.setStyle(QStyleFactory::create(QStringLiteral("Fusion")));
app.setFont(QFont(QStringLiteral("Microsoft YaHei UI"), 11));
app.setStyleSheet(QStringLiteral(
"QMainWindow { background: #f5f7fb; }"
"QLabel { color: #23364c; }"
"QLabel#titleLabel { font-size: 27px; font-weight: bold; color: #173b63; }"
"QLabel#subtitleLabel, QLabel#helpLabel { color: #52657a; }"
"QGroupBox { background: white; border: 1px solid #d6e0eb;"
" border-radius: 8px; margin-top: 12px; padding: 18px 12px 12px; }"
"QGroupBox::title { subcontrol-origin: margin; left: 12px; padding: 0 6px; }"
"QLineEdit, QSpinBox, QComboBox { min-height: 32px; padding: 3px 8px; }"
"QLineEdit, QSpinBox, QComboBox, QPlainTextEdit, QListWidget"
" { background: white; color: #23364c; }"
"QPushButton { min-height: 34px; padding: 4px 14px; }"
"QPushButton#convertButton, QPushButton#startButton, QPushButton#drawButton,"
" QPushButton#applyButton, QPushButton#groupsButton { background: #255c93;"
" color: white; border: 1px solid #255c93; border-radius: 5px; }"
"QPushButton:disabled, QPushButton#convertButton:disabled,"
" QPushButton#startButton:disabled, QPushButton#drawButton:disabled,"
" QPushButton#applyButton:disabled, QPushButton#groupsButton:disabled"
" { color: #8995a3; background: #e6ebf1; border-color: #d6e0eb; }"
"QLabel#statusLabel { color: #185376; }"
"QLineEdit#resultEdit { font-size: 23px; min-height: 45px; color: #175e53; }"
"QLabel#timeLabel { font: bold 76px 'Consolas'; color: #173b63; }"
"QProgressBar { border: 1px solid #d6e0eb; border-radius: 6px; height: 14px; }"
"QProgressBar::chunk { background: #2b8a7d; border-radius: 5px; }"));
}
+61
View File
@@ -0,0 +1,61 @@
#include "countdown.h"
Countdown::Countdown(qint64 durationMs)
: m_duration(qMax<qint64>(0, durationMs)), m_remainingAtStart(m_duration)
{
}
bool Countdown::setDuration(qint64 durationMs)
{
if (m_state == State::Running || m_state == State::Paused || durationMs < 0)
return false;
m_duration = durationMs;
reset();
return true;
}
void Countdown::start(qint64 nowMs)
{
if (m_state == State::Running || m_duration <= 0)
return;
if (m_state != State::Paused)
m_remainingAtStart = m_duration;
m_segmentStarted = nowMs;
m_state = State::Running;
}
qint64 Countdown::remaining(qint64 nowMs) const
{
if (m_state == State::Finished)
return 0;
if (m_state != State::Running)
return m_remainingAtStart;
const qint64 elapsed = qMax<qint64>(0, nowMs - m_segmentStarted);
return qMax<qint64>(0, m_remainingAtStart - elapsed);
}
void Countdown::update(qint64 nowMs)
{
if (m_state == State::Running && remaining(nowMs) == 0) {
m_remainingAtStart = 0;
m_state = State::Finished;
}
}
void Countdown::pause(qint64 nowMs)
{
update(nowMs);
if (m_state != State::Running)
return;
// 先保存这一刻的剩余时间,再切换状态,暂停期间不继续扣减。
m_remainingAtStart = remaining(nowMs);
m_state = State::Paused;
}
void Countdown::reset()
{
m_remainingAtStart = m_duration;
m_segmentStarted = 0;
m_state = State::Ready;
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include <QtGlobal>
// 界面负责取得当前时间,模型只负责计算;测试可传入确定的毫秒数。
class Countdown {
public:
enum class State { Ready, Running, Paused, Finished };
explicit Countdown(qint64 durationMs = 300000);
bool setDuration(qint64 durationMs);
void start(qint64 nowMs);
void pause(qint64 nowMs);
void reset();
void update(qint64 nowMs);
qint64 remaining(qint64 nowMs) const;
qint64 duration() const { return m_duration; }
State state() const { return m_state; }
private:
qint64 m_duration;
qint64 m_remainingAtStart;
qint64 m_segmentStarted = 0;
State m_state = State::Ready;
};
+19
View File
@@ -0,0 +1,19 @@
#include "mainwindow.h"
#include "appearance.h"
#include <QApplication>
#include <QFont>
#include <QStyleFactory>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
QApplication app(argc, argv);
applyAppearance(app);
MainWindow window;
window.show();
return app.exec();
}
+91
View File
@@ -0,0 +1,91 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>
#include <QShortcut>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_clock.start();
m_refreshTimer.setInterval(100);
connect(&m_refreshTimer, &QTimer::timeout, this, &MainWindow::refreshDisplay);
for (QSpinBox *spin : {ui->hoursSpin, ui->minutesSpin, ui->secondsSpin})
connect(spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, &MainWindow::durationChanged);
connect(ui->startButton, &QPushButton::clicked, this, [this] {
m_countdown.start(m_clock.elapsed());
refreshDisplay();
});
connect(ui->pauseButton, &QPushButton::clicked, this, [this] {
const auto previousState = m_countdown.state();
m_countdown.pause(m_clock.elapsed());
if (previousState == Countdown::State::Running
&& m_countdown.state() == Countdown::State::Finished
&& ui->soundCheck->isChecked())
QApplication::beep();
refreshDisplay();
});
connect(ui->resetButton, &QPushButton::clicked, this, [this] {
m_countdown.reset();
refreshDisplay();
});
connect(ui->fullscreenButton, &QPushButton::clicked, this, [this] {
if (isFullScreen()) showNormal(); else showFullScreen();
ui->fullscreenButton->setText(isFullScreen() ? tr("退出全屏") : tr("全屏显示"));
});
auto *escape = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(escape, &QShortcut::activated, this, [this] {
if (isFullScreen()) {
showNormal();
ui->fullscreenButton->setText(tr("全屏显示"));
}
});
durationChanged();
}
MainWindow::~MainWindow() { delete ui; }
void MainWindow::durationChanged()
{
const qint64 seconds = ui->hoursSpin->value() * 3600
+ ui->minutesSpin->value() * 60 + ui->secondsSpin->value();
m_countdown.setDuration(seconds * 1000);
refreshDisplay();
}
void MainWindow::refreshDisplay()
{
const qint64 now = m_clock.elapsed();
const auto previousState = m_countdown.state();
m_countdown.update(now);
const auto state = m_countdown.state();
const bool running = state == Countdown::State::Running;
const bool paused = state == Countdown::State::Paused;
if (running && !m_refreshTimer.isActive()) m_refreshTimer.start();
if (!running) m_refreshTimer.stop();
const qint64 remaining = m_countdown.remaining(now);
// 向上取整:还剩 1 毫秒时显示 1 秒,真正到时才显示零。
const qint64 seconds = (remaining + 999) / 1000;
ui->timeLabel->setText(QStringLiteral("%1:%2:%3")
.arg(seconds / 3600, 2, 10, QLatin1Char('0'))
.arg((seconds / 60) % 60, 2, 10, QLatin1Char('0'))
.arg(seconds % 60, 2, 10, QLatin1Char('0')));
ui->progressBar->setValue(m_countdown.duration() == 0 ? 0
: static_cast<int>(remaining * 1000 / m_countdown.duration()));
ui->durationGroup->setEnabled(!running && !paused);
ui->startButton->setEnabled(!running && m_countdown.duration() > 0);
ui->pauseButton->setEnabled(running);
ui->startButton->setText(paused ? tr("继续") :
state == Countdown::State::Finished ? tr("重新开始") : tr("开始"));
if (running) ui->statusLabel->setText(tr("正在计时"));
else if (paused) ui->statusLabel->setText(tr("已暂停 · 点击“继续”接着计时"));
else if (state == Countdown::State::Finished)
ui->statusLabel->setText(tr("时间到!本轮计时已结束。"));
else ui->statusLabel->setText(m_countdown.duration() > 0
? tr("准备就绪 · 点击“开始”") : tr("请先设置大于 0 的时长。"));
if (previousState == Countdown::State::Running && state == Countdown::State::Finished
&& ui->soundCheck->isChecked())
QApplication::beep();
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <QMainWindow>
#include <QElapsedTimer>
#include <QTimer>
#include "countdown.h"
namespace Ui { class MainWindow; }
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private:
Ui::MainWindow *ui;
Countdown m_countdown;
QElapsedTimer m_clock;
QTimer m_refreshTimer;
void durationChanged();
void refreshDisplay();
};
+242
View File
@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="windowTitle">
<string>课堂计时器</string>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>820</width>
<height>650</height>
</rect>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="rootLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>24</number>
</property>
<property name="topMargin">
<number>24</number>
</property>
<property name="rightMargin">
<number>24</number>
</property>
<property name="bottomMargin">
<number>24</number>
</property>
<item>
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>课堂计时器</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="subtitleLabel">
<property name="text">
<string>用于课堂练习、分组讨论与限时展示。</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="durationGroup">
<property name="title">
<string>设置时长</string>
</property>
<layout class="QGridLayout" name="durationLayout">
<property name="spacing">
<number>12</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="hoursSpinLabel">
<property name="text">
<string>小时</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QSpinBox" name="hoursSpin">
<property name="maximum">
<number>23</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="minutesSpinLabel">
<property name="text">
<string>分钟</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="minutesSpin">
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="secondsSpinLabel">
<property name="text">
<string>秒</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="secondsSpin">
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>00:05:00</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>135</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="maximum">
<number>1000</number>
</property>
<property name="value">
<number>1000</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>准备就绪</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>45</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="timerActions">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>开始</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pauseButton">
<property name="text">
<string>暂停</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetButton">
<property name="text">
<string>复位</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="fullscreenButton">
<property name="text">
<string>全屏显示</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="soundCheck">
<property name="text">
<string>到时播放系统提示音</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="helpLabel">
<property name="text">
<string>计时中和暂停时锁定时长;点击“复位”后可重新设置。全屏时按 Esc 返回。</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources />
<connections />
</ui>
@@ -0,0 +1,100 @@
#include <QtTest>
#include <QSpinBox>
#include <QPushButton>
#include <QLabel>
#include <QCheckBox>
#include "countdown.h"
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include "appearance.h"
static void savePreview(QWidget &window, const QString &fileName)
{
const QString folder = QString::fromLocal8Bit(qgetenv("QT_TEACHING_SCREENSHOTS"));
if (folder.isEmpty()) return;
QDir().mkpath(folder);
window.show();
QApplication::processEvents();
QVERIFY(window.grab().save(QDir(folder).filePath(fileName)));
}
class TimerTest : public QObject {
Q_OBJECT
private slots:
void initTestCase() { applyAppearance(*qApp); }
void pauseResumeAndDrift()
{
Countdown model(10000);
model.start(100);
model.start(500); // 重复开始不能重新计时。
QCOMPARE(model.remaining(2600), qint64(7500));
model.pause(2600);
QCOMPARE(model.remaining(90000), qint64(7500));
QVERIFY(!model.setDuration(2000));
model.start(100000);
QCOMPARE(model.remaining(102500), qint64(5000));
model.update(107500);
QVERIFY(model.state() == Countdown::State::Finished);
QCOMPARE(model.remaining(107500), qint64(0));
model.start(200000);
QCOMPARE(model.remaining(200000), qint64(10000));
}
void delayedRefreshAndReset()
{
Countdown model(5000);
model.start(0);
model.update(100);
model.update(4300);
QCOMPARE(model.remaining(4300), qint64(700));
model.update(9999);
QCOMPARE(model.remaining(9999), qint64(0));
QVERIFY(model.state() == Countdown::State::Finished);
model.reset();
QVERIFY(model.state() == Countdown::State::Ready);
QCOMPARE(model.remaining(9999), qint64(5000));
}
void zeroAndDeadlinePause()
{
Countdown model(0);
model.start(0);
QVERIFY(model.state() == Countdown::State::Ready);
QVERIFY(!model.setDuration(-1));
QVERIFY(model.setDuration(1000));
model.start(10);
model.pause(1010);
QVERIFY(model.state() == Countdown::State::Finished);
}
void uiWorkflow()
{
MainWindow w; w.show();
auto *start = w.findChild<QPushButton *>(QStringLiteral("startButton"));
auto *pause = w.findChild<QPushButton *>(QStringLiteral("pauseButton"));
auto *reset = w.findChild<QPushButton *>(QStringLiteral("resetButton"));
auto *minutes = w.findChild<QSpinBox *>(QStringLiteral("minutesSpin"));
auto *seconds = w.findChild<QSpinBox *>(QStringLiteral("secondsSpin"));
auto *time = w.findChild<QLabel *>(QStringLiteral("timeLabel"));
w.findChild<QCheckBox *>(QStringLiteral("soundCheck"))->setChecked(false);
savePreview(w, QStringLiteral("02_timer.png"));
minutes->setValue(0); seconds->setValue(1);
QTest::mouseClick(start, Qt::LeftButton);
QVERIFY(!minutes->isEnabled());
QTest::mouseClick(pause, Qt::LeftButton);
QCOMPARE(start->text(), QStringLiteral("继续"));
const QString pausedText = time->text();
QTest::qWait(150);
QCOMPARE(time->text(), pausedText);
QTest::mouseClick(start, Qt::LeftButton);
QTRY_COMPARE(time->text(), QStringLiteral("00:00:00"));
QVERIFY(w.findChild<QLabel *>(QStringLiteral("statusLabel"))->text().contains(QStringLiteral("时间到")));
QVERIFY(!pause->isEnabled());
QTest::mouseClick(reset, Qt::LeftButton);
QCOMPARE(time->text(), QStringLiteral("00:00:01"));
QVERIFY(minutes->isEnabled());
seconds->setValue(0);
QVERIFY(!start->isEnabled());
}
};
QTEST_MAIN(TimerTest)
#include "test_ClassroomTimer.moc"
@@ -0,0 +1,11 @@
QT += widgets testlib
CONFIG += c++14 console testcase
greaterThan(QT_MAJOR_VERSION, 5): CONFIG += c++17
TEMPLATE = app
TARGET = test_ClassroomTimer
INCLUDEPATH += ..
SOURCES += test_ClassroomTimer.cpp ../mainwindow.cpp ../countdown.cpp
HEADERS += ../mainwindow.h ../countdown.h
FORMS += ../mainwindow.ui
msvc: QMAKE_CXXFLAGS += /utf-8