Kaynağa Gözat

feat: 增加工程未保存变更保护

main
suyu 1 ay önce
ebeveyn
işleme
5923bcd5dd
3 değiştirilmiş dosya ile 118 ekleme ve 1 silme
  1. +68
    -1
      app/src/ui/main_window.cpp
  2. +4
    -0
      app/src/ui/main_window.h
  3. +46
    -0
      app/tests/main_window_tests.cpp

+ 68
- 1
app/src/ui/main_window.cpp Dosyayı Görüntüle

@@ -26,6 +26,7 @@
#include <QActionGroup>
#include <QAbstractSpinBox>
#include <QApplication>
#include <QCloseEvent>
#include <QEvent>
#include <QFileDialog>
#include <QGraphicsScene>
@@ -315,6 +316,16 @@ bool MainWindow::eventFilter(QObject *watched, QEvent *event)
return QMainWindow::eventFilter(watched, event);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
if (!confirmSaveBeforeDestructiveAction())
{
event->ignore();
return;
}
QMainWindow::closeEvent(event);
}

const std::string &MainWindow::currentHmiPageId() const
{
return current_hmi_page_id_;
@@ -1035,6 +1046,44 @@ void MainWindow::refreshProjectUi()
{
project_workspace_controller_->refresh();
updateEditActions();
updateWindowTitle();
}

void MainWindow::updateWindowTitle()
{
QString title = tr("综合平台编程器");
if (project_service_.isModified())
{
title += QStringLiteral("*");
}
setWindowTitle(title);
}

bool MainWindow::confirmSaveBeforeDestructiveAction()
{
if (!project_service_.isModified())
{
return true;
}

const QString project_name = fromUtf8(project_service_.project().metadata.name);
const QMessageBox::StandardButton choice = QMessageBox::warning(
this,
tr("工程尚未保存"),
tr("工程“%1”有未保存的修改,是否先保存?").arg(project_name),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
QMessageBox::Save);
if (choice == QMessageBox::Cancel)
{
return false;
}
if (choice == QMessageBox::Discard)
{
return true;
}

saveProject();
return !project_service_.isModified();
}

void MainWindow::updateProjectTreeActions()
@@ -1245,6 +1294,10 @@ void MainWindow::deleteSelectedLogicObject()

void MainWindow::createNewProject()
{
if (!confirmSaveBeforeDestructiveAction())
{
return;
}
const QString name = QInputDialog::getText(
this, tr("新建工程"), tr("工程名称"));
if (name.isEmpty())
@@ -1273,12 +1326,18 @@ void MainWindow::createNewProject()

void MainWindow::saveProject()
{
if (!project_service_.hasCurrentFile())
{
saveProjectAs();
return;
}
const ProjectOperationResult result = project_service_.save();
if (!result.succeeded)
{
saveProjectAs();
showProjectResult(tr("保存工程"), fromUtf8(result.message), false);
return;
}
updateWindowTitle();
showProjectResult(tr("保存工程"), tr("工程已保存"), true);
}

@@ -1291,6 +1350,10 @@ void MainWindow::saveProjectAs()
return;
}
const ProjectOperationResult result = project_service_.saveAs(toUtf8(path));
if (result.succeeded)
{
updateWindowTitle();
}
showProjectResult(
tr("保存工程"),
result.succeeded ? tr("工程已保存") : fromUtf8(result.message),
@@ -1299,6 +1362,10 @@ void MainWindow::saveProjectAs()

void MainWindow::loadProject()
{
if (!confirmSaveBeforeDestructiveAction())
{
return;
}
const QString path = QFileDialog::getOpenFileName(
this, tr("加载工程"), {}, tr("工程文件 (*.json)"));
if (path.isEmpty())


+ 4
- 0
app/src/ui/main_window.h Dosyayı Görüntüle

@@ -20,6 +20,7 @@
QT_BEGIN_NAMESPACE
class QAction;
class QActionGroup;
class QCloseEvent;
class QEvent;
class QLabel;

@@ -94,6 +95,7 @@ public:

protected:
bool eventFilter(QObject *watched, QEvent *event) override;
void closeEvent(QCloseEvent *event) override;

private:
// 配置菜单和工具栏动作
@@ -117,6 +119,8 @@ private:
void clearActiveSelection();
void updateEditActions();
void clearEditorHistories();
void updateWindowTitle();
bool confirmSaveBeforeDestructiveAction();
// 刷新工程树和当前 HMI 页面信息
void refreshProjectUi();
void updateProjectTreeActions();


+ 46
- 0
app/tests/main_window_tests.cpp Dosyayı Görüntüle

@@ -560,6 +560,51 @@ void testRuntimeAlarmListInteraction()
"AlarmList must become visible again after returning to editing");
}

void testWindowTitleTracksUnsavedProjectChanges()
{
TestProjectStorage storage;
ProjectService project_service(storage);
HmiEditorService editor_service(project_service);
LogicEditorService logic_editor_service(project_service);
require(editor_service.ensureDefaultPage().succeeded,
"window-title test must create a default HMI page");
require(logic_editor_service.ensureDefaultLogic().succeeded,
"window-title test must create a default control logic");
require(project_service.saveAs("window-title-test.json").succeeded,
"window-title fixture must start from a saved project");

VirtualRegisterRepository repository;
HmiRuntimeService runtime_service(repository);
AlarmEditorService alarm_editor_service(project_service);
AlarmService alarm_service(project_service, repository);
RegisterCommentService register_comment_service(project_service);
OfflineSimulationService simulation_service(repository);
RuntimeModeService mode_service(project_service, simulation_service);
RegisterMonitorService monitor_service(repository);
MainWindow window(
mode_service,
project_service,
editor_service,
logic_editor_service,
runtime_service,
alarm_editor_service,
alarm_service,
register_comment_service,
monitor_service);

require(!window.windowTitle().endsWith(QLatin1Char('*')),
"a saved project title must not show an unsaved marker");
requiredChild<QAction>(window, "addLabelAction")->trigger();
require(project_service.isModified()
&& window.windowTitle().endsWith(QLatin1Char('*')),
"editing the project must immediately add an unsaved marker");

requiredChild<QAction>(window, "saveProjectAction")->trigger();
require(!project_service.isModified()
&& !window.windowTitle().endsWith(QLatin1Char('*')),
"saving the project must immediately remove the unsaved marker");
}

void testModeActionsControlEditingAvailability()
{
// 验证模式动作会同步禁用编辑入口,并在失败时恢复当前选择
@@ -1482,6 +1527,7 @@ int main(int argc, char *argv[])
testRuntimeProgressBarRendering();
testRuntimePageJumpDoesNotRequireRegisterWritePermission();
testRuntimeAlarmListInteraction();
testWindowTitleTracksUnsavedProjectChanges();
testModeActionsControlEditingAvailability();
testEdgeTimerPropertyEditingAndParallelMenu();
testPlcConfigurationUsesDialog();


Yükleniyor…
İptal
Kaydet