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()) {
100 return createErrorResponse(QString(
"Cannot write IC file (target location is read-only): %1").arg(fullPath),
105 result[
"name"] = name;
106 result[
"filename"] = icFileName;
107 result[
"path"] = fullPath;
108 result[
"elements_count"] = elements.size();
109 result[
"description"] = description;
110 result[
"message"] =
"IC created successfully from current circuit";
113 },
"create IC", requestId);
116QJsonObject ICHandler::handleInstantiateIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
127 QString icName = params.value(
"ic_name").toString();
128 if (icName.isEmpty()) {
137 const bool isAbsolute = QFileInfo(icName).isAbsolute();
139 if (icName.contains(
"..")) {
143 }
else if (!isBareFileName(icName)) {
144 return createErrorResponse(
"IC name must not contain path separators or directory components",
149 int x = qRound(params.value(
"x").toDouble() / snap) * snap;
150 int y = qRound(params.value(
"y").toDouble() / snap) * snap;
151 QString label = params.value(
"label").toString(icName);
154 QString icFileName = icName +
".panda";
155 QString fullPath = isAbsolute ? icFileName :
m_mainWindow->currentDir().absoluteFilePath(icFileName);
157 if (!QFile::exists(fullPath)) {
162 auto ic = std::make_unique<IC>();
164 const QString icDirectory = QFileInfo(fullPath).absolutePath();
165 const bool inlineMode = params.value(
"inline").toBool(
false);
170 QFile file(fullPath);
171 if (!file.open(QIODevice::ReadOnly)) {
175 QByteArray fileBytes = file.readAll();
178 QString blobName = params.value(
"blob_name").toString();
179 if (blobName.isEmpty()) {
180 blobName = QFileInfo(fullPath).baseName();
181 }
else if (!isBareFileName(blobName)) {
184 return createErrorResponse(
"blob_name must not contain path separators or directory components",
189 if (reg->hasBlob(blobName)) {
190 return createErrorResponse(QString(
"Blob name collision: an embedded IC named '%1' already exists. "
191 "Use blob_name parameter to specify a different name.").arg(blobName),
195 icPtr = reg->createEmbeddedIC(blobName, fileBytes, icDirectory);
200 ic->loadFile(fullPath, icDirectory);
201 icPtr = ic.release();
211 result[
"element_id"] = icPtr->
id();
212 result[
"ic_name"] = icName;
213 result[
"filename"] = icFileName;
214 result[
"label"] = label;
215 result[
"position"] = QJsonObject{{
"x", x}, {
"y", y}};
216 result[
"input_count"] = icPtr->
inputSize();
218 result[
"width"] = bounds.width();
219 result[
"height"] = bounds.height();
221 result[
"inline"] =
true;
222 result[
"blob_name"] = icPtr->
blobName();
224 result[
"message"] =
"IC instantiated successfully";
227 },
"instantiate IC", requestId);
230QJsonObject ICHandler::handleListICs(
const QJsonObject &,
const QJsonValue &requestId)
237 filters <<
"*.panda";
239 const QFileInfoList pandaFiles = currentDir.entryInfoList(filters, QDir::Files);
241 for (
const QFileInfo &fileInfo : pandaFiles) {
243 QFile file(fileInfo.absoluteFilePath());
244 if (!file.open(QIODevice::ReadOnly)) {
248 QDataStream stream(&file);
251 if (!version.isNull()) {
253 icInfo[
"name"] = fileInfo.baseName();
254 icInfo[
"filename"] = fileInfo.fileName();
255 icInfo[
"path"] = fileInfo.absoluteFilePath();
256 icInfo[
"size"] = fileInfo.size();
257 icInfo[
"modified"] = fileInfo.lastModified().toString(Qt::ISODate);
261 tempIC.
loadFile(fileInfo.absoluteFilePath(), fileInfo.absolutePath());
262 icInfo[
"input_count"] = tempIC.
inputSize();
264 icInfo[
"has_valid_definition"] =
true;
266 icInfo[
"has_valid_definition"] =
false;
267 icInfo[
"input_count"] = 0;
268 icInfo[
"output_count"] = 0;
271 icsArray.append(icInfo);
282 result[
"ics"] = icsArray;
283 result[
"directory"] = currentDir.absolutePath();
284 result[
"count"] = icsArray.size();
287 },
"list ICs", requestId);
290QJsonObject ICHandler::handleEmbedIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
301 int elementId = params.value(
"element_id").toInt();
302 if (elementId <= 0) {
307 auto *item = scene->
itemById(elementId);
313 auto *elm =
dynamic_cast<GraphicElement *
>(item);
314 if (!elm || elm->elementType() != ElementType::IC) {
318 auto *ic =
static_cast<IC *
>(elm);
320 if (ic->isEmbedded()) {
324 if (ic->file().isEmpty()) {
328 const QString contextDir = scene->
contextDir();
329 if (contextDir.isEmpty()) {
333 const QString resolvedPath = QDir(contextDir).absoluteFilePath(ic->file());
334 QFile file(resolvedPath);
335 if (!file.open(QIODevice::ReadOnly)) {
339 QByteArray fileBytes = file.readAll();
342 QString blobName = params.value(
"blob_name").toString();
343 if (blobName.isEmpty()) {
344 blobName = QFileInfo(resolvedPath).baseName();
345 }
else if (!isBareFileName(blobName)) {
349 return createErrorResponse(
"blob_name must not contain path separators or directory components",
354 if (reg->hasBlob(blobName)) {
355 return createErrorResponse(QString(
"Blob name collision: an embedded IC named '%1' already exists").arg(blobName),
359 const int count = reg->
embedICsByFile(ic->file(), fileBytes, blobName);
362 result[
"blob_name"] = blobName;
363 result[
"converted_count"] = count;
364 result[
"input_count"] = ic->inputSize();
365 result[
"output_count"] = ic->outputSize();
366 result[
"blob_size"] = fileBytes.size();
367 result[
"message"] = QString(
"Embedded %1 IC(s) as '%2'").arg(count).arg(blobName);
370 },
"embed IC", requestId);
373QJsonObject ICHandler::handleExtractIC(
const QJsonObject ¶ms,
const QJsonValue &requestId)
384 const QString contextDir = scene->
contextDir();
385 if (contextDir.isEmpty()) {
389 const QString blobName = params.value(
"blob_name").toString();
390 if (blobName.isEmpty()) {
396 if (!reg->hasBlob(blobName)) {
397 return createErrorResponse(QString(
"No embedded IC with blob name '%1' found").arg(blobName),
401 QString fileName = params.value(
"file_name").toString();
402 if (fileName.isEmpty()) {
403 fileName = QDir(contextDir).absoluteFilePath(blobName +
".panda");
404 }
else if (QFileInfo(fileName).isRelative()) {
405 fileName = QDir(contextDir).absoluteFilePath(fileName);
407 if (!fileName.endsWith(
".panda")) {
408 fileName.append(
".panda");
418 const QString cleanContextDir = QDir::cleanPath(QDir(contextDir).absolutePath());
419 const QString cleanFileName = QDir::cleanPath(fileName);
420 if (cleanFileName != cleanContextDir && !cleanFileName.startsWith(cleanContextDir +
'/')) {
421 return createErrorResponse(
"file_name must resolve to a location inside the project directory",
424 fileName = cleanFileName;
426 if (QFile::exists(fileName) && !params.value(
"overwrite").toBool()) {
427 return createErrorResponse(QString(
"File '%1' already exists. Set overwrite=true to replace it.").arg(fileName),
431 const int count = reg->extractToFile(blobName, fileName);
434 result[
"blob_name"] = blobName;
435 result[
"file_name"] = fileName;
436 result[
"converted_count"] = count;
437 result[
"message"] = QString(
"Extracted %1 IC(s) to '%2'").arg(count).arg(fileName);
440 },
"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.