Skip to content

Commit a97618b

Browse files
committed
feat: 添加工作台会话页面,支持多标签会话和会话配置复制功能
- 新增工作台会话页面,允许同时调试多个设备。 - 实现标签页功能,每个标签页为独立的串口工作台实例,支持复制当前会话配置。 - 更新主窗口以使用新的工作台会话页面,调整相关连接和设置。 - 优化工作台页面构造,支持会话恢复和自动打开选项。
1 parent 487b34e commit a97618b

9 files changed

Lines changed: 343 additions & 14 deletions

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ add_executable(FluentSerialAssistant
7979
src/app/view/settings_page.h
8080
src/app/view/workbench_page.cpp
8181
src/app/view/workbench_page.h
82+
src/app/view/workbench_sessions_page.cpp
83+
src/app/view/workbench_sessions_page.h
8284
src/app/view/workbench/workbench_page_checksum.cpp
8385
src/app/view/workbench/workbench_page_auto_reply.cpp
8486
src/app/view/workbench/workbench_page_connection.cpp

docs/feature-roadmap.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@
209209

210210
用于同时调试多个设备或多个 USB 转串口。
211211

212+
状态:已实现。
213+
214+
终端页已经支持多标签会话。每个标签页是独立的串口工作台实例,新增标签会复制当前会话配置,关闭标签会释放对应串口和文件资源。
215+
212216
需求:
213217

214218
- 支持同时打开多个串口。

src/app/view/main_window.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "app/view/main_window.h"
22

33
#include "app/view/settings_page.h"
4-
#include "app/view/workbench_page.h"
4+
#include "app/view/workbench_sessions_page.h"
55

66
#include <QtGui/QCloseEvent>
77
#include <QtGui/QGuiApplication>
@@ -43,16 +43,17 @@ void MainWindow::closeEvent(QCloseEvent *event)
4343

4444
void MainWindow::populateInterfaces()
4545
{
46-
m_workbenchPage = new WorkbenchPage(this);
46+
m_workbenchPage = new WorkbenchSessionsPage(this);
4747
m_workbenchPage->setObjectName(QStringLiteral("workbench"));
4848
addSubInterface(m_workbenchPage, icon(FluentIcon::CommandPrompt), QStringLiteral("终端"));
49-
connect(m_workbenchPage, &WorkbenchPage::settingsRequested, this,
49+
connect(m_workbenchPage, &WorkbenchSessionsPage::settingsRequested, this,
5050
[this]() { switchTo(QStringLiteral("settings")); });
5151

5252
auto *settingsPage = new SettingsPage(this);
5353
settingsPage->setObjectName(QStringLiteral("settings"));
5454
addSubInterface(settingsPage, icon(FluentIcon::Setting), QStringLiteral("设置"), QIcon(),
5555
NavigationItemPosition::Bottom);
5656
connect(settingsPage, &SettingsPage::terminalRequested, this, [this]() { switchTo(QStringLiteral("workbench")); });
57-
connect(settingsPage, &SettingsPage::terminalFontChanged, m_workbenchPage, &WorkbenchPage::setTerminalFontFamily);
57+
connect(settingsPage, &SettingsPage::terminalFontChanged, m_workbenchPage,
58+
&WorkbenchSessionsPage::setTerminalFontFamily);
5859
}

src/app/view/main_window.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include <FluentQtWidgets/FluentQtWidgets.h>
44

5-
class WorkbenchPage;
5+
class WorkbenchSessionsPage;
66

77
class MainWindow : public FluentQt::MSFluentWindow
88
{
@@ -18,5 +18,5 @@ class MainWindow : public FluentQt::MSFluentWindow
1818
private:
1919
void populateInterfaces();
2020

21-
WorkbenchPage *m_workbenchPage = nullptr;
21+
WorkbenchSessionsPage *m_workbenchPage = nullptr;
2222
};

src/app/view/workbench/workbench_page_state.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,185 @@ void WorkbenchPage::saveSettings() const
297297
saveAutoReplyRules();
298298
}
299299

300+
void WorkbenchPage::copySessionConfigFrom(const WorkbenchPage &source)
301+
{
302+
refreshPorts();
303+
304+
const int portIndex = m_portCombo->findData(source.m_portCombo->currentData());
305+
if (portIndex >= 0) {
306+
m_portCombo->setCurrentIndex(portIndex);
307+
}
308+
m_baudCombo->setCurrentText(source.m_baudCombo->currentText());
309+
m_dataBitsCombo->setCurrentText(source.m_dataBitsCombo->currentText());
310+
const int parityIndex = m_parityCombo->findData(source.m_parityCombo->currentData());
311+
if (parityIndex >= 0) {
312+
m_parityCombo->setCurrentIndex(parityIndex);
313+
}
314+
const int stopIndex = m_stopBitsCombo->findData(source.m_stopBitsCombo->currentData());
315+
if (stopIndex >= 0) {
316+
m_stopBitsCombo->setCurrentIndex(stopIndex);
317+
}
318+
const int flowIndex = m_flowControlCombo->findData(source.m_flowControlCombo->currentData());
319+
if (flowIndex >= 0) {
320+
m_flowControlCombo->setCurrentIndex(flowIndex);
321+
}
322+
m_rtsCheck->setChecked(source.m_rtsCheck->isChecked());
323+
m_dtrCheck->setChecked(source.m_dtrCheck->isChecked());
324+
m_autoOpenCheck->setChecked(source.m_autoOpenCheck->isChecked());
325+
326+
if (m_displayModeSegment->contains(source.currentDisplayMode())) {
327+
m_displayModeSegment->setCurrentItem(source.currentDisplayMode());
328+
}
329+
selectEncodingOption(m_receiveEncodingCombo, source.receiveEncodingKey());
330+
{
331+
const QSignalBlocker blocker(m_saveReceiveCheck);
332+
m_saveReceiveCheck->setChecked(source.m_saveReceiveCheck->isChecked());
333+
}
334+
closeReceiveCapture();
335+
m_autoScrollCheck->setChecked(source.m_autoScrollCheck->isChecked());
336+
m_timestampCheck->setChecked(source.m_timestampCheck->isChecked());
337+
m_pauseCheck->setChecked(false);
338+
const int frameModeIndex = m_frameModeCombo->findData(source.frameBreakModeKey());
339+
if (frameModeIndex >= 0) {
340+
m_frameModeCombo->setCurrentIndex(frameModeIndex);
341+
}
342+
m_framePatternEdit->setText(source.m_framePatternEdit->text());
343+
m_frameFixedLengthSpin->setValue(source.m_frameFixedLengthSpin->value());
344+
m_frameBreakIntervalSpin->setValue(source.m_frameBreakIntervalSpin->value());
345+
m_autoFrameBreakCheck->setChecked(source.m_autoFrameBreakCheck->isChecked());
346+
updateFrameControlState();
347+
348+
const int autoLogFormatIndex = m_autoLogFormatCombo->findData(source.autoLogFormatKey());
349+
if (autoLogFormatIndex >= 0) {
350+
m_autoLogFormatCombo->setCurrentIndex(autoLogFormatIndex);
351+
}
352+
m_autoLogMaxSizeSpin->setValue(source.m_autoLogMaxSizeSpin->value());
353+
{
354+
const QSignalBlocker blocker(m_autoLogCheck);
355+
m_autoLogCheck->setChecked(source.m_autoLogCheck->isChecked());
356+
}
357+
resetAutoLogSession();
358+
updateAutoLogStatus();
359+
360+
m_hexSendCheck->setChecked(source.m_hexSendCheck->isChecked());
361+
m_showTxCheck->setChecked(source.m_showTxCheck->isChecked());
362+
m_txColorButton->setColor(source.selectedTxColor());
363+
m_txColorButton->setEnabled(m_showTxCheck->isChecked());
364+
m_loopCheck->setChecked(false);
365+
m_autoReconnectCheck->setChecked(source.m_autoReconnectCheck->isChecked());
366+
m_loopIntervalSpin->setValue(source.m_loopIntervalSpin->value());
367+
selectEncodingOption(m_sendEncodingCombo, source.sendEncodingKey());
368+
const int checksumAlgorithmIndex = m_checksumAlgorithmCombo->findData(source.checksumAlgorithmKey());
369+
if (checksumAlgorithmIndex >= 0) {
370+
m_checksumAlgorithmCombo->setCurrentIndex(checksumAlgorithmIndex);
371+
}
372+
const int checksumByteOrderIndex =
373+
m_checksumByteOrderCombo->findData(AppChecksum::byteOrderKey(source.checksumByteOrder()));
374+
if (checksumByteOrderIndex >= 0) {
375+
m_checksumByteOrderCombo->setCurrentIndex(checksumByteOrderIndex);
376+
}
377+
m_checksumAppendCheck->setChecked(source.m_checksumAppendCheck->isChecked());
378+
const int lineEndingIndex = m_lineEndingCombo->findData(source.selectedLineEndingKey());
379+
if (lineEndingIndex >= 0) {
380+
m_lineEndingCombo->setCurrentIndex(lineEndingIndex);
381+
}
382+
383+
m_sendEdit->setPlainText(source.m_sendEdit->toPlainText());
384+
m_sendHistory = source.m_sendHistory;
385+
updateHistoryCombo();
386+
387+
m_sendPackets = source.m_sendPackets;
388+
m_packetGroupEdit->setText(source.m_packetGroupEdit->text());
389+
m_packetNameEdit->setText(source.m_packetNameEdit->text());
390+
m_packetNoteEdit->setText(source.m_packetNoteEdit->text());
391+
m_packetPayloadEdit->setPlainText(source.m_packetPayloadEdit->toPlainText());
392+
const int packetModeIndex = m_packetModeCombo->findData(source.m_packetModeCombo->currentData());
393+
if (packetModeIndex >= 0) {
394+
m_packetModeCombo->setCurrentIndex(packetModeIndex);
395+
}
396+
const int packetLineEndingIndex = m_packetLineEndingCombo->findData(source.m_packetLineEndingCombo->currentData());
397+
if (packetLineEndingIndex >= 0) {
398+
m_packetLineEndingCombo->setCurrentIndex(packetLineEndingIndex);
399+
}
400+
m_packetEnabledCheck->setChecked(source.m_packetEnabledCheck->isChecked());
401+
updatePacketTable();
402+
403+
const int modbusFunctionIndex = m_modbusFunctionCombo->findData(source.m_modbusFunctionCombo->currentData());
404+
if (modbusFunctionIndex >= 0) {
405+
m_modbusFunctionCombo->setCurrentIndex(modbusFunctionIndex);
406+
}
407+
m_modbusSlaveSpin->setValue(source.m_modbusSlaveSpin->value());
408+
m_modbusAddressSpin->setValue(source.m_modbusAddressSpin->value());
409+
m_modbusQuantitySpin->setValue(source.m_modbusQuantitySpin->value());
410+
m_modbusValuesEdit->setPlainText(source.m_modbusValuesEdit->toPlainText());
411+
updateModbusUi();
412+
413+
m_macroSteps = source.m_macroSteps;
414+
m_macroNameEdit->setText(source.m_macroNameEdit->text());
415+
m_macroPayloadEdit->setPlainText(source.m_macroPayloadEdit->toPlainText());
416+
m_macroExpectedEdit->setText(source.m_macroExpectedEdit->text());
417+
const int macroModeIndex = m_macroModeCombo->findData(source.m_macroModeCombo->currentData());
418+
if (macroModeIndex >= 0) {
419+
m_macroModeCombo->setCurrentIndex(macroModeIndex);
420+
}
421+
const int macroLineEndingIndex = m_macroLineEndingCombo->findData(source.m_macroLineEndingCombo->currentData());
422+
if (macroLineEndingIndex >= 0) {
423+
m_macroLineEndingCombo->setCurrentIndex(macroLineEndingIndex);
424+
}
425+
const int macroResponseModeIndex =
426+
m_macroResponseModeCombo->findData(source.m_macroResponseModeCombo->currentData());
427+
if (macroResponseModeIndex >= 0) {
428+
m_macroResponseModeCombo->setCurrentIndex(macroResponseModeIndex);
429+
}
430+
m_macroTimeoutSpin->setValue(source.m_macroTimeoutSpin->value());
431+
m_macroDelaySpin->setValue(source.m_macroDelaySpin->value());
432+
m_macroLoopCountSpin->setValue(source.m_macroLoopCountSpin->value());
433+
m_macroAbortOnFailureCheck->setChecked(source.m_macroAbortOnFailureCheck->isChecked());
434+
updateMacroTable();
435+
436+
m_autoReplyRules = source.m_autoReplyRules;
437+
m_autoReplyNameEdit->setText(source.m_autoReplyNameEdit->text());
438+
m_autoReplyPatternEdit->setText(source.m_autoReplyPatternEdit->text());
439+
m_autoReplyPayloadEdit->setPlainText(source.m_autoReplyPayloadEdit->toPlainText());
440+
const int autoReplyMatchIndex =
441+
m_autoReplyMatchModeCombo->findData(source.m_autoReplyMatchModeCombo->currentData());
442+
if (autoReplyMatchIndex >= 0) {
443+
m_autoReplyMatchModeCombo->setCurrentIndex(autoReplyMatchIndex);
444+
}
445+
const int autoReplyResponseIndex =
446+
m_autoReplyResponseModeCombo->findData(source.m_autoReplyResponseModeCombo->currentData());
447+
if (autoReplyResponseIndex >= 0) {
448+
m_autoReplyResponseModeCombo->setCurrentIndex(autoReplyResponseIndex);
449+
}
450+
const int autoReplyLineEndingIndex =
451+
m_autoReplyLineEndingCombo->findData(source.m_autoReplyLineEndingCombo->currentData());
452+
if (autoReplyLineEndingIndex >= 0) {
453+
m_autoReplyLineEndingCombo->setCurrentIndex(autoReplyLineEndingIndex);
454+
}
455+
m_autoReplyDelaySpin->setValue(source.m_autoReplyDelaySpin->value());
456+
m_autoReplyEnabledCheck->setChecked(source.m_autoReplyEnabledCheck->isChecked());
457+
updateAutoReplyTable();
458+
459+
m_filePathEdit->setText(source.m_filePathEdit->text());
460+
m_fileChunkSizeSpin->setValue(source.m_fileChunkSizeSpin->value());
461+
m_fileIntervalSpin->setValue(source.m_fileIntervalSpin->value());
462+
updateFileSendUi(false);
463+
updateFileProgress();
464+
465+
m_records.clear();
466+
m_pendingRecordIndexes.clear();
467+
m_terminalSearchMatches.clear();
468+
m_terminalCurrentSearchMatch = -1;
469+
m_terminalStartRecord = 0;
470+
m_rxCount = 0;
471+
m_txCount = 0;
472+
m_lastStatsRxCount = 0;
473+
m_lastStatsTxCount = 0;
474+
m_terminalView->clear();
475+
updateCounters();
476+
updateRateStats();
477+
}
478+
300479
void WorkbenchPage::setTerminalFontFamily(const QString &family) { applyTerminalFont(family); }
301480

302481
void WorkbenchPage::applyTerminalFont(const QString &family)

src/app/view/workbench_page.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using namespace FluentQt;
44
using namespace WorkbenchPagePrivate;
55

6-
WorkbenchPage::WorkbenchPage(QWidget *parent)
6+
WorkbenchPage::WorkbenchPage(QWidget *parent, bool restoreSavedSession, bool allowAutoOpen)
77
: AppPage(QStringLiteral("终端"), QStringLiteral("连接串口设备,查看收发记录,并执行文本或 HEX 数据发送。"), parent,
88
false)
99
{
@@ -26,17 +26,31 @@ WorkbenchPage::WorkbenchPage(QWidget *parent)
2626
connect(ThemeManager::instance(), &ThemeManager::effectiveThemeChanged, this, [this]() { renderTerminal(); });
2727

2828
refreshPorts();
29-
loadSendHistory();
30-
loadSendPackets();
31-
loadMacroSteps();
32-
loadAutoReplyRules();
33-
restoreSettings();
29+
if (restoreSavedSession) {
30+
loadSendHistory();
31+
loadSendPackets();
32+
loadMacroSteps();
33+
loadAutoReplyRules();
34+
restoreSettings();
35+
} else {
36+
updateFrameControlState();
37+
updateModbusUi();
38+
updatePacketTable();
39+
updateMacroTable();
40+
updateAutoReplyTable();
41+
updatePacketActionState();
42+
updateMacroActionState();
43+
updateAutoReplyActionState();
44+
updateFileSendUi(false);
45+
updateFileProgress();
46+
updateAutoLogStatus();
47+
}
3448
updateConnectionUi(false);
3549
updateCounters();
3650
updateRateStats();
3751
updateHistoryCombo();
3852

39-
if (m_autoOpenCheck && m_autoOpenCheck->isChecked() && !currentSerialConfig().portName.isEmpty()) {
53+
if (allowAutoOpen && m_autoOpenCheck && m_autoOpenCheck->isChecked() && !currentSerialConfig().portName.isEmpty()) {
4054
QTimer::singleShot(250, this, &WorkbenchPage::onConnectClicked);
4155
}
4256
}

src/app/view/workbench_page.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@ class WorkbenchPage : public AppPage
2222
Q_OBJECT
2323

2424
public:
25-
explicit WorkbenchPage(QWidget *parent = nullptr);
25+
explicit WorkbenchPage(QWidget *parent = nullptr, bool restoreSavedSession = true, bool allowAutoOpen = true);
2626
~WorkbenchPage() override;
2727

2828
void saveSettings() const;
29+
void copySessionConfigFrom(const WorkbenchPage &source);
2930

3031
public slots:
3132
void setTerminalFontFamily(const QString &family);

0 commit comments

Comments
 (0)