Skip to content

Commit 0c1df85

Browse files
claudejgeudens
authored andcommitted
Restore MBC import functionality with strict isolation
Re-adds the ModbusControl (.mbc) file import subsystem removed in ecc0e52. The only two entry points from main code into MBC code are the 'Import MBC File...' menu action and drag-and-drop of .mbc files on the main window — both routed through MainWindow::showMbcImportDialog(). New files: - src/importexport/mbcfileimporter — XML parser for .mbc files - src/importexport/mbcregisterdata — register data container with toExpression() - src/models/mbcregistermodel — checkable QAbstractTableModel for parsed registers - src/models/mbcregisterfilter — QSortFilterProxyModel for tab/text filtering - src/models/mbcupdatemodel — detects expression/label mismatches vs GraphDataModel - src/dialogs/importmbcdialog — import dialog with drag-and-drop and update tab - src/dialogs/mbcheader — QHeaderView subclass with select-all checkbox - data/example.mbc — sample MBC project file - tests/importexport/tst_mbcfileimporter — comprehensive parser tests - tests/importexport/tst_mbcregisterfilter — filter model tests - tests/models/tst_mbcregistermodel — register model tests - tests/models/tst_mbcupdatemodel — update model tests Minimal changes to existing files: - GuiModel: add lastMbcImportedFile getter/setter - MainWindow: add showMbcImportDialog slot, connect menu action, enable/disable action per GUI state, handle .mbc in handleFileOpen https://claude.ai/code/session_01KvuREfVMg7MnzkpLBg8NVz
1 parent 5581dd5 commit 0c1df85

32 files changed

Lines changed: 3744 additions & 0 deletions

data/example.mbc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<modbuscontrol>
2+
<Size>
3+
<Width>820</Width>
4+
<Height>600</Height>
5+
</Size>
6+
7+
<Modbus>
8+
<Port>COM3</Port>
9+
<Baudrate>9600</Baudrate>
10+
<Parity>none</Parity>
11+
<SlaveID>1</SlaveID>
12+
<SampleRate>500</SampleRate>
13+
<ResponseTimeout>1000</ResponseTimeout>
14+
<MaximumFramesize>129</MaximumFramesize>
15+
<HoldingregisterStart>40001</HoldingregisterStart>
16+
<WriteHoldingregisterFC>16</WriteHoldingregisterFC>
17+
<Value32BitEndian>little</Value32BitEndian>
18+
<ShowType>true</ShowType>
19+
<ShowReadwrite>true</ShowReadwrite>
20+
</Modbus>
21+
22+
<modbustcpserver>
23+
<enable>true</enable>
24+
<port>502</port>
25+
</modbustcpserver>
26+
27+
<tab>
28+
<name>General Info and Commands</name>
29+
<var><reg>40001</reg><text>Data 1</text><type>uint16</type><rw>r</rw></var>
30+
<var><reg>40002</reg><text>Data 2</text><type>uint16</type><rw>r</rw></var>
31+
<var><reg>40003</reg><text>Data 3</text><type>uint16</type><rw>r</rw></var>
32+
</tab>
33+
34+
<tab>
35+
<name>System State</name>
36+
<var><reg>40004</reg><text>System Pressure</text><type>uint16</type><decimals>2</decimals><rw>r</rw><bcol>225,225,255</bcol></var>
37+
<var><reg>*</reg><text>Inlet Pressure</text><type>uint16</type><decimals>2</decimals><rw>r</rw><bcol>225,225,255</bcol></var>
38+
<var><reg>*</reg><text>Setpoint</text><type>uint16</type><decimals>1</decimals><rw>rw</rw><bcol>255,225,225</bcol></var>
39+
<var><reg>40007</reg><text>Bandwidth</text><type>uint16</type><decimals>2</decimals><rw>rw</rw><bcol>255,225,225</bcol></var>
40+
41+
<var><reg>40008</reg><text>Data large</text><type>uint32</type><rw>r</rw></var>
42+
<var><reg>40009</reg><text>Float32</text><type>float32</type><rw>r</rw></var>
43+
<var><reg>40010</reg><text>Signed data</text><type>int16</type><rw>r</rw></var>
44+
<var><reg>40011</reg><text>Large signed data</text><type>int32</type><rw>r</rw></var>
45+
</tab>
46+
47+
</modbuscontrol>

src/dialogs/importmbcdialog.cpp

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
#include "importmbcdialog.h"
2+
3+
#include "customwidgets/actionbuttondelegate.h"
4+
#include "dialogs/mbcheader.h"
5+
#include "dialogs/ui_importmbcdialog.h"
6+
#include "importexport/mbcfileimporter.h"
7+
#include "models/graphdatamodel.h"
8+
#include "models/guimodel.h"
9+
#include "models/mbcregisterfilter.h"
10+
#include "models/mbcregistermodel.h"
11+
#include "models/mbcupdatemodel.h"
12+
#include "util/fileselectionhelper.h"
13+
#include "util/util.h"
14+
15+
#include <QFileDialog>
16+
17+
/*!
18+
* \brief Constructs the ImportMbcDialog.
19+
* \param pGuiModel Used to read/write the last imported MBC file path.
20+
* \param pGraphDatamodel Used to detect update suggestions and to add new registers.
21+
* \param parent Parent widget.
22+
*/
23+
ImportMbcDialog::ImportMbcDialog(GuiModel* pGuiModel, GraphDataModel* pGraphDatamodel, QWidget* parent)
24+
: QDialog(parent), _pUi(new Ui::ImportMbcDialog), _pGuiModel(pGuiModel), _pGraphDataModel(pGraphDatamodel)
25+
{
26+
_pUi->setupUi(this);
27+
28+
_pMbcUpdateModel = new MbcUpdateModel(_pGraphDataModel);
29+
30+
_pTabProxyFilter = new MbcRegisterFilter();
31+
_pTabProxyFilter->setSourceModel(&_mbcRegisterModel);
32+
33+
/* Disable question mark button */
34+
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
35+
36+
this->setAcceptDrops(true);
37+
_pUi->lineTextFilter->setAcceptDrops(false);
38+
39+
// Setup registerView
40+
_pUi->tblMbcRegisters->setModel(_pTabProxyFilter);
41+
_pUi->tblMbcRegisters->setSortingEnabled(false);
42+
43+
auto mbcHeader = new MbcHeader(Qt::Horizontal, _pUi->tblMbcRegisters);
44+
_pUi->tblMbcRegisters->setHorizontalHeader(mbcHeader);
45+
46+
/* Don't stretch columns */
47+
_pUi->tblMbcRegisters->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
48+
49+
/* Except following columns */
50+
_pUi->tblMbcRegisters->horizontalHeader()->setSectionResizeMode(MbcRegisterModel::cColumnText,
51+
QHeaderView::Stretch);
52+
_pUi->tblMbcRegisters->horizontalHeader()->setSectionResizeMode(MbcRegisterModel::cColumnTab, QHeaderView::Stretch);
53+
54+
// Select using click, shift and control
55+
_pUi->tblMbcRegisters->setSelectionBehavior(QAbstractItemView::SelectItems);
56+
_pUi->tblMbcRegisters->setSelectionMode(QAbstractItemView::NoSelection);
57+
58+
_pUi->tblMbcRegisters->setFocusPolicy(Qt::NoFocus);
59+
60+
_pUi->tblMbcRegisters->setStyle(&_centeredBoxStyle);
61+
62+
// setup register
63+
_pUi->tblMbcUpdate->setModel(_pMbcUpdateModel);
64+
_pUi->tblMbcUpdate->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
65+
66+
// Select using click, shift and control
67+
_pUi->tblMbcUpdate->setSelectionBehavior(QAbstractItemView::SelectItems);
68+
_pUi->tblMbcUpdate->setSelectionMode(QAbstractItemView::NoSelection);
69+
70+
_pUi->tblMbcUpdate->setFocusPolicy(Qt::NoFocus);
71+
72+
_pUpdateDelegate = std::make_unique<ActionButtonDelegate>(_pUi->tblMbcUpdate);
73+
_pUpdateDelegate->setCharacter(QChar(0x2190));
74+
connect(_pUpdateDelegate.get(), &ActionButtonDelegate::clicked, this, &ImportMbcDialog::handleAcceptUpdate);
75+
76+
_pUi->tblMbcUpdate->setItemDelegateForColumn(MbcUpdateModel::cColumnUpdateExpression, _pUpdateDelegate.get());
77+
_pUi->tblMbcUpdate->setItemDelegateForColumn(MbcUpdateModel::cColumnUpdateText, _pUpdateDelegate.get());
78+
79+
connect(_pUi->btnSelectMbcFile, &QToolButton::clicked, this, &ImportMbcDialog::selectMbcFile);
80+
connect(_pUi->btnImportRegisters, &QPushButton::clicked, this, &ImportMbcDialog::importSelectedRegisters);
81+
connect(&_mbcRegisterModel, &QAbstractItemModel::dataChanged, this, &ImportMbcDialog::registerDataChanged);
82+
connect(_pTabProxyFilter, &MbcRegisterFilter::dataChanged, this, &ImportMbcDialog::visibleItemsDataChanged);
83+
connect(mbcHeader, &MbcHeader::selectAllClicked, this, &ImportMbcDialog::handleSelectAllClicked);
84+
85+
connect(_pUi->cmbTabFilter, &QComboBox::currentTextChanged, _pTabProxyFilter, &MbcRegisterFilter::setTab);
86+
connect(_pUi->lineTextFilter, &QLineEdit::textChanged, this, &ImportMbcDialog::updateTextFilter);
87+
}
88+
89+
ImportMbcDialog::~ImportMbcDialog()
90+
{
91+
delete _pUi;
92+
}
93+
94+
/*!
95+
* \brief Opens the dialog, auto-loading the last imported file when available.
96+
* \return QDialog::exec() return value.
97+
*/
98+
int ImportMbcDialog::exec()
99+
{
100+
QString filePath = _pGuiModel->lastMbcImportedFile();
101+
if (!filePath.isEmpty())
102+
{
103+
/* Auto load with supplied path */
104+
updateMbcRegisters(filePath);
105+
106+
_pUi->lineMbcfile->setText(filePath);
107+
}
108+
else
109+
{
110+
/* Skip auto load: no file path */
111+
_pUi->lineMbcfile->setText("");
112+
}
113+
114+
return QDialog::exec();
115+
}
116+
117+
void ImportMbcDialog::updateTextFilter()
118+
{
119+
auto checkHeight = _pUi->tblMbcRegisters->rowHeight(0) / 2;
120+
QModelIndex topRow = _pUi->tblMbcRegisters->indexAt(QPoint(checkHeight, checkHeight));
121+
122+
auto currentTopModelIndex = _pTabProxyFilter->mapToSource(topRow);
123+
_pTabProxyFilter->setTextFilter(_pUi->lineTextFilter->text());
124+
125+
auto newTopModelIndex = _pTabProxyFilter->mapFromSource(currentTopModelIndex);
126+
127+
_pUi->tblMbcRegisters->scrollTo(newTopModelIndex, QAbstractItemView::PositionAtTop);
128+
}
129+
130+
void ImportMbcDialog::selectMbcFile()
131+
{
132+
QFileDialog dialog(this);
133+
FileSelectionHelper::configureFileDialog(&dialog, FileSelectionHelper::DIALOG_TYPE_OPEN,
134+
FileSelectionHelper::FILE_TYPE_MBC);
135+
136+
QString selectedFile = FileSelectionHelper::showDialog(&dialog);
137+
if (!selectedFile.isEmpty())
138+
{
139+
_pUi->lineMbcfile->setText(selectedFile);
140+
_pGuiModel->setLastMbcImportedFile(selectedFile);
141+
142+
updateMbcRegisters(selectedFile);
143+
}
144+
}
145+
146+
void ImportMbcDialog::importSelectedRegisters()
147+
{
148+
QList<GraphData> regList = _mbcRegisterModel.selectedRegisterList();
149+
150+
if (!regList.isEmpty())
151+
{
152+
_pGraphDataModel->add(regList);
153+
}
154+
155+
setSelectedSelectionstate(Qt::Unchecked);
156+
}
157+
158+
void ImportMbcDialog::visibleItemsDataChanged()
159+
{
160+
const int regCount = _pTabProxyFilter->rowCount();
161+
162+
bool checked = true;
163+
bool unchecked = true;
164+
165+
for (int idx = 0; idx < regCount; idx++)
166+
{
167+
QModelIndex idxOfItem = _pTabProxyFilter->index(idx, MbcRegisterModel::cColumnSelected);
168+
if (_pTabProxyFilter->data(idxOfItem, Qt::CheckStateRole) == Qt::Checked)
169+
{
170+
unchecked = false;
171+
}
172+
173+
if (_pTabProxyFilter->data(idxOfItem, Qt::CheckStateRole) == Qt::Unchecked)
174+
{
175+
checked = false;
176+
}
177+
}
178+
179+
Qt::CheckState selectAllState;
180+
if (checked)
181+
{
182+
selectAllState = Qt::Checked;
183+
}
184+
else if (unchecked)
185+
{
186+
selectAllState = Qt::Unchecked;
187+
}
188+
else
189+
{
190+
selectAllState = Qt::PartiallyChecked;
191+
}
192+
193+
_mbcRegisterModel.setHeaderData(MbcRegisterModel::cColumnSelected, Qt::Horizontal, selectAllState,
194+
Qt::CheckStateRole);
195+
}
196+
197+
void ImportMbcDialog::registerDataChanged()
198+
{
199+
const quint32 count = _mbcRegisterModel.selectedRegisterCount();
200+
if (count == 1)
201+
{
202+
_pUi->lblSelectedCount->setText(QString("You have selected %1 register.").arg(count));
203+
}
204+
else
205+
{
206+
_pUi->lblSelectedCount->setText(QString("You have selected %1 registers.").arg(count));
207+
}
208+
}
209+
210+
void ImportMbcDialog::handleSelectAllClicked(Qt::CheckState state)
211+
{
212+
if (state == Qt::Unchecked)
213+
{
214+
setSelectedSelectionstate(Qt::Unchecked);
215+
}
216+
else if (state == Qt::Checked)
217+
{
218+
setSelectedSelectionstate(Qt::Checked);
219+
}
220+
else
221+
{
222+
// No need to handle PartialChecked
223+
}
224+
}
225+
226+
void ImportMbcDialog::dragEnterEvent(QDragEnterEvent* e)
227+
{
228+
if (e->mimeData()->hasUrls())
229+
{
230+
e->acceptProposedAction();
231+
}
232+
}
233+
234+
void ImportMbcDialog::dropEvent(QDropEvent* e)
235+
{
236+
const QString filename(e->mimeData()->urls().constLast().toLocalFile());
237+
238+
_pUi->lineMbcfile->setText(filename);
239+
_pGuiModel->setLastMbcImportedFile(filename);
240+
241+
updateMbcRegisters(filename);
242+
}
243+
244+
void ImportMbcDialog::closeEvent(QCloseEvent* event)
245+
{
246+
if (confirmClose())
247+
{
248+
event->accept(); // allow window to close
249+
}
250+
else
251+
{
252+
event->ignore(); // block window close
253+
}
254+
}
255+
256+
void ImportMbcDialog::reject()
257+
{
258+
if (confirmClose())
259+
{
260+
QDialog::reject(); // close via reject
261+
}
262+
// else: do nothing, dialog stays open
263+
}
264+
265+
void ImportMbcDialog::setSelectedSelectionstate(Qt::CheckState state)
266+
{
267+
QList<QModelIndex> indexList;
268+
for (int idx = 0; idx < _pTabProxyFilter->rowCount(); idx++)
269+
{
270+
QModelIndex idxOfItem = _pTabProxyFilter->index(idx, MbcRegisterModel::cColumnSelected);
271+
indexList.append(_pTabProxyFilter->mapToSource(idxOfItem));
272+
}
273+
274+
_mbcRegisterModel.setSelectionstate(indexList, state);
275+
}
276+
277+
void ImportMbcDialog::updateMbcRegisters(QString filePath)
278+
{
279+
QFile file(filePath);
280+
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
281+
{
282+
QTextStream in(&file);
283+
QString mbcFileContent = in.readAll();
284+
MbcFileImporter fileImporter(&mbcFileContent);
285+
QList<MbcRegisterData> registerList = fileImporter.registerList();
286+
QStringList tabList = fileImporter.tabList();
287+
288+
/* Clear data from table widget */
289+
_mbcRegisterModel.reset();
290+
registerDataChanged();
291+
292+
if (registerList.size() > 0)
293+
{
294+
_mbcRegisterModel.fill(registerList, tabList);
295+
_pMbcUpdateModel->setMbcRegisters(registerList);
296+
297+
/* Update combo box */
298+
_pUi->cmbTabFilter->clear();
299+
_pUi->cmbTabFilter->addItem(MbcRegisterFilter::cTabNoFilter);
300+
_pUi->cmbTabFilter->addItems(tabList);
301+
}
302+
}
303+
else
304+
{
305+
Util::showError(tr("The file (\"%1\") can't be read.").arg(filePath));
306+
}
307+
}
308+
309+
void ImportMbcDialog::handleAcceptUpdate(const QModelIndex& index)
310+
{
311+
if (!index.isValid())
312+
{
313+
return;
314+
}
315+
316+
if (index.column() == MbcUpdateModel::cColumnUpdateExpression)
317+
{
318+
_pGraphDataModel->setExpression(index.row(), _pMbcUpdateModel->data(index).toString());
319+
}
320+
else if (index.column() == MbcUpdateModel::cColumnUpdateText)
321+
{
322+
_pGraphDataModel->setLabel(index.row(), _pMbcUpdateModel->data(index).toString());
323+
}
324+
else
325+
{
326+
// nothing to do
327+
}
328+
}
329+
330+
bool ImportMbcDialog::confirmClose()
331+
{
332+
bool bClose = true;
333+
if (_mbcRegisterModel.selectedRegisterCount() > 0)
334+
{
335+
auto reply = QMessageBox::question(
336+
this, tr("Unimported registers"),
337+
tr("Some registers are selected, but aren't imported yet.\n\nDo you really want to close this dialog?"),
338+
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
339+
bClose = reply == QMessageBox::Yes;
340+
341+
if (!bClose)
342+
{
343+
/* User has selected to NOT close the dialog, so switch to first tab */
344+
_pUi->tabWidget->setCurrentIndex(0);
345+
}
346+
}
347+
return bClose;
348+
}

0 commit comments

Comments
 (0)