26bool isBareFileName(
const QString &name)
28 return name !=
"." && name !=
".." && QFileInfo(name).fileName() == name
29 && !name.contains(
'/') && !name.contains(
'\\');
41 if (command ==
"create_ic") {
42 return handleCreateIC(params, requestId);
43 }
else if (command ==
"instantiate_ic") {
44 return handleInstantiateIC(params, requestId);
45 }
else if (command ==
"list_ics") {
46 return handleListICs(params, requestId);
47 }
else if (command ==
"embed_ic") {
48 return handleEmbedIC(params, requestId);
49 }
else if (command ==
"extract_ic") {
50 return handleExtractIC(params, requestId);
57QJsonObject ICHandler::handleCreateIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
68 QString name = params.value(
"name").toString();
69 QString description = params.value(
"description").toString(
"");
75 if (!isBareFileName(name)) {
76 return createErrorResponse(
"IC name must not contain path separators or directory components",
81 QString icFileName = name +
".panda";
82 QString fullPath =
m_mainWindow->currentDir().absoluteFilePath(icFileName);
84 if (QFile::exists(fullPath)) {
89 const auto elements = scene->
elements();
90 if (elements.isEmpty()) {
108 return createErrorResponse(QString(
"Cannot write IC file (target location is read-only): %1").arg(fullPath),
113 result[
"name"] = name;
114 result[
"filename"] = icFileName;
115 result[
"path"] = fullPath;
116 result[
"elements_count"] = elements.size();
117 result[
"description"] = description;
118 result[
"message"] =
"IC created successfully from current circuit";
121 },
"create IC", requestId);
124QJsonObject ICHandler::handleInstantiateIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
135 QString icName = params.value(
"ic_name").toString();
136 if (icName.isEmpty()) {
145 const bool isAbsolute = QFileInfo(icName).isAbsolute();
147 if (icName.contains(
"..")) {
151 }
else if (!isBareFileName(icName)) {
152 return createErrorResponse(
"IC name must not contain path separators or directory components",
157 int x = qRound(params.value(
"x").toDouble() / snap) * snap;
158 int y = qRound(params.value(
"y").toDouble() / snap) * snap;
159 QString label = params.value(
"label").toString(icName);
162 QString icFileName = icName +
".panda";
163 QString fullPath = isAbsolute ? icFileName :
m_mainWindow->currentDir().absoluteFilePath(icFileName);
165 if (!QFile::exists(fullPath)) {
170 auto ic = std::make_unique<IC>();
172 const QString icDirectory = QFileInfo(fullPath).absolutePath();
173 const bool inlineMode = params.value(
"inline").toBool(
false);
178 QFile file(fullPath);
179 if (!file.open(QIODevice::ReadOnly)) {
183 QByteArray fileBytes = file.readAll();
186 QString blobName = params.value(
"blob_name").toString();
187 if (blobName.isEmpty()) {
188 blobName = QFileInfo(fullPath).baseName();
189 }
else if (!isBareFileName(blobName)) {
192 return createErrorResponse(
"blob_name must not contain path separators or directory components",
197 if (reg->hasBlob(blobName)) {
198 return createErrorResponse(QString(
"Blob name collision: an embedded IC named '%1' already exists. "
199 "Use blob_name parameter to specify a different name.").arg(blobName),
203 icPtr = reg->createEmbeddedIC(blobName, fileBytes, icDirectory);
208 ic->loadFile(fullPath, icDirectory);
209 icPtr = ic.release();
219 result[
"element_id"] = icPtr->
id();
220 result[
"ic_name"] = icName;
221 result[
"filename"] = icFileName;
222 result[
"label"] = label;
223 result[
"position"] = QJsonObject{{
"x", x}, {
"y", y}};
224 result[
"input_count"] = icPtr->
inputSize();
226 result[
"width"] = bounds.width();
227 result[
"height"] = bounds.height();
229 result[
"inline"] =
true;
230 result[
"blob_name"] = icPtr->
blobName();
232 result[
"message"] =
"IC instantiated successfully";
235 },
"instantiate IC", requestId);
238QJsonObject ICHandler::handleListICs(
const QJsonObject &,
const QJsonValue &requestId)
245 filters <<
"*.panda";
247 const QFileInfoList pandaFiles = currentDir.entryInfoList(filters, QDir::Files);
249 for (
const QFileInfo &fileInfo : pandaFiles) {
251 QFile file(fileInfo.absoluteFilePath());
252 if (!file.open(QIODevice::ReadOnly)) {
256 QDataStream stream(&file);
259 if (!version.isNull()) {
261 icInfo[
"name"] = fileInfo.baseName();
262 icInfo[
"filename"] = fileInfo.fileName();
263 icInfo[
"path"] = fileInfo.absoluteFilePath();
264 icInfo[
"size"] = fileInfo.size();
265 icInfo[
"modified"] = fileInfo.lastModified().toString(Qt::ISODate);
269 tempIC.
loadFile(fileInfo.absoluteFilePath(), fileInfo.absolutePath());
270 icInfo[
"input_count"] = tempIC.
inputSize();
272 icInfo[
"has_valid_definition"] =
true;
274 icInfo[
"has_valid_definition"] =
false;
275 icInfo[
"input_count"] = 0;
276 icInfo[
"output_count"] = 0;
279 icsArray.append(icInfo);
290 result[
"ics"] = icsArray;
291 result[
"directory"] = currentDir.absolutePath();
292 result[
"count"] = icsArray.size();
295 },
"list ICs", requestId);
298QJsonObject ICHandler::handleEmbedIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
309 int elementId = params.value(
"element_id").toInt();
310 if (elementId <= 0) {
315 auto *item = scene->
itemById(elementId);
321 auto *elm =
dynamic_cast<GraphicElement *
>(item);
322 if (!elm || elm->elementType() != ElementType::IC) {
326 auto *ic =
static_cast<IC *
>(elm);
328 if (ic->isEmbedded()) {
332 if (ic->file().isEmpty()) {
336 const QString contextDir = scene->
contextDir();
337 if (contextDir.isEmpty()) {
341 const QString resolvedPath = QDir(contextDir).absoluteFilePath(ic->file());
342 QFile file(resolvedPath);
343 if (!file.open(QIODevice::ReadOnly)) {
347 QByteArray fileBytes = file.readAll();
350 QString blobName = params.value(
"blob_name").toString();
351 if (blobName.isEmpty()) {
352 blobName = QFileInfo(resolvedPath).baseName();
353 }
else if (!isBareFileName(blobName)) {
357 return createErrorResponse(
"blob_name must not contain path separators or directory components",
362 if (reg->hasBlob(blobName)) {
363 return createErrorResponse(QString(
"Blob name collision: an embedded IC named '%1' already exists").arg(blobName),
367 const int count = reg->
embedICsByFile(ic->file(), fileBytes, blobName);
370 result[
"blob_name"] = blobName;
371 result[
"converted_count"] = count;
372 result[
"input_count"] = ic->inputSize();
373 result[
"output_count"] = ic->outputSize();
374 result[
"blob_size"] = fileBytes.size();
375 result[
"message"] = QString(
"Embedded %1 IC(s) as '%2'").arg(count).arg(blobName);
378 },
"embed IC", requestId);
381QJsonObject ICHandler::handleExtractIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
392 const QString contextDir = scene->
contextDir();
393 if (contextDir.isEmpty()) {
397 const QString blobName = params.value(
"blob_name").toString();
398 if (blobName.isEmpty()) {
404 if (!reg->hasBlob(blobName)) {
405 return createErrorResponse(QString(
"No embedded IC with blob name '%1' found").arg(blobName),
409 QString fileName = params.value(
"file_name").toString();
410 if (fileName.isEmpty()) {
411 fileName = QDir(contextDir).absoluteFilePath(blobName +
".panda");
412 }
else if (QFileInfo(fileName).isRelative()) {
413 fileName = QDir(contextDir).absoluteFilePath(fileName);
415 if (!fileName.endsWith(
".panda")) {
416 fileName.append(
".panda");
426 const QString cleanContextDir = QDir::cleanPath(QDir(contextDir).absolutePath());
427 const QString cleanFileName = QDir::cleanPath(fileName);
428 if (cleanFileName != cleanContextDir && !cleanFileName.startsWith(cleanContextDir +
'/')) {
429 return createErrorResponse(
"file_name must resolve to a location inside the project directory",
432 fileName = cleanFileName;
434 if (QFile::exists(fileName) && !params.value(
"overwrite").toBool()) {
435 return createErrorResponse(QString(
"File '%1' already exists. Set overwrite=true to replace it.").arg(fileName),
439 const int count = reg->extractToFile(blobName, fileName);
442 result[
"blob_name"] = blobName;
443 result[
"file_name"] = fileName;
444 result[
"converted_count"] = count;
445 result[
"message"] = QString(
"Extracted %1 IC(s) to '%2'").arg(count).arg(fileName);
448 },
"extract IC", requestId);
All QUndoCommand subclasses and the CommandUtils helper namespace.
Shared numeric constants used across layers.
IC definition registry with file watching and embedded blob storage.
Integrated Circuit (IC) graphic element that encapsulates a sub-circuit file.
Main application window providing menus, toolbars, and tab management.
Main circuit editing scene with undo/redo and user interaction.
Circuit and waveform file serialization/deserialization utilities.
WorkSpace widget: the complete circuit editing environment for one tab.
QJsonObject createSuccessResponse(const QJsonObject &result={}, const QJsonValue &requestId=QJsonValue()) const
QJsonObject tryCommand(Fn &&fn, const QString &action, const QJsonValue &requestId=QJsonValue())
Wraps fn in a try/catch, returning an error response on exception.
BaseHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject createErrorResponse(const QString &error, const QJsonValue &requestId=QJsonValue(), int code=JsonRpcError::InternalError) const
MainWindow * m_mainWindow
bool validateParameters(const QJsonObject ¶ms, const QStringList &required) const
int inputSize() const
Returns the current number of input ports.
void setLabel(const QString &label)
Sets the label text to label and refreshes the display.
int outputSize() const
Returns the current number of output ports.
ICHandler(MainWindow *mainWindow, const MCPValidator *validator)
QJsonObject handleCommand(const QString &command, const QJsonObject ¶ms, const QJsonValue &requestId) override
int embedICsByFile(const QString &fileName, const QByteArray &fileBytes, const QString &blobName)
Converts all file-backed IC elements referencing fileName to embedded ICs using blobName.
const QString & blobName() const override
Returns the blob name for embedded ICs, empty if file-backed.
QRectF boundingRect() const override
void loadFile(const QString &fileName, const QString &contextDir={})
Loads the IC circuit from fileName and rebuilds the logic mapping.
int id() const
Returns the unique integer identifier of this item, or -1 if unassigned.
JSON Schema validator for MCP commands and responses using native json-schema-validator.
The top-level application window hosting the tab bar, menus, element palette, and editor.
const QVector< GraphicElement * > elements() const
Returns all graphic elements in the scene.
void receiveCommand(QUndoCommand *cmd)
Pushes cmd onto the undo stack (immediately executes its redo()).
QString contextDir() const override
Returns the directory of the .panda file associated with this scene.
ItemWithId * itemById(int id) const
Returns the item registered under id, or nullptr if not found.
ICRegistry * icRegistry()
Returns the IC definition registry for this scene.
static QVersionNumber readPandaHeader(QDataStream &stream)
Reads and validates the .panda circuit file header; returns the stored version number.
constexpr int gridSize
Scene grid unit in pixels (elements snap to gridSize/2).
constexpr int SceneNotAvailable
No active circuit scene to operate on.
constexpr int MethodNotFound
The requested method does not exist or is unavailable.
constexpr int InternalError
Internal JSON-RPC error (last-resort).
constexpr int ElementNotFound
Referenced element id does not exist in the scene.
constexpr int FileError
File save/load/path error.
constexpr int ValidationError
Semantic validation failure (e.g. port index out of range, enum value not allowed).
constexpr int InvalidParams
Invalid method parameters (missing required, wrong type, etc.).
constexpr int IcError
IC create / instantiate / embed / extract failure.