Add dynamic file filter to Open dialog
- Add TypeRegistry::supportedExtensions() to extract file extensions from loaded XScript definitions by parsing criteria blocks - Implement File > Open action with QFileDialog - Generate filter string dynamically from all root type definitions - Include "All Supported Files" and "All Files" filter options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
bebaeff322
commit
64db5a19ed
@ -297,8 +297,100 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
// TODO: Implement New action
|
||||
});
|
||||
|
||||
connect(actionOpen, &QAction::triggered, this, []() {
|
||||
// TODO: Implement Open action
|
||||
connect(actionOpen, &QAction::triggered, this, [this]() {
|
||||
// Build filter string from loaded definitions
|
||||
QMap<QString, QString> exts = mTypeRegistry.supportedExtensions();
|
||||
|
||||
QStringList filterParts;
|
||||
QStringList allExts;
|
||||
|
||||
for (auto it = exts.begin(); it != exts.end(); ++it) {
|
||||
const QString& ext = it.key();
|
||||
const QString& displayName = it.value();
|
||||
filterParts.append(QString("%1 (*.%2)").arg(displayName, ext));
|
||||
allExts.append(QString("*.%1").arg(ext));
|
||||
}
|
||||
|
||||
// Sort filters alphabetically by display name
|
||||
filterParts.sort(Qt::CaseInsensitive);
|
||||
|
||||
// Add "All Supported Files" at the beginning
|
||||
QString allFilter = QString("All Supported Files (%1)").arg(allExts.join(" "));
|
||||
filterParts.prepend(allFilter);
|
||||
|
||||
// Add "All Files" at the end
|
||||
filterParts.append("All Files (*)");
|
||||
|
||||
QString filter = filterParts.join(";;");
|
||||
|
||||
QStringList filePaths = QFileDialog::getOpenFileNames(
|
||||
this,
|
||||
"Open File",
|
||||
QString(),
|
||||
filter
|
||||
);
|
||||
|
||||
for (const QString& path : filePaths) {
|
||||
const QString fileName = QFileInfo(path).fileName();
|
||||
|
||||
QFile inputFile(path);
|
||||
if (!inputFile.open(QIODevice::ReadOnly)) {
|
||||
LogManager::instance().addError(QString("Failed to open: %1").arg(path));
|
||||
continue;
|
||||
}
|
||||
|
||||
LogManager::instance().addEntry(QString("[FILE] Analyzing: %1").arg(fileName));
|
||||
|
||||
const QString rootType = mTypeRegistry.chooseType(&inputFile, path);
|
||||
LogManager::instance().addEntry(QString("[FILE] Matched type '%1' for file: %2").arg(rootType).arg(fileName));
|
||||
|
||||
if (rootType.isEmpty()) {
|
||||
LogManager::instance().addError(QString("No matching definition for: %1").arg(path));
|
||||
continue;
|
||||
}
|
||||
|
||||
inputFile.seek(0);
|
||||
LogManager::instance().addLine();
|
||||
LogManager::instance().addEntry(QString("========== PARSING %1 ==========").arg(fileName));
|
||||
LogManager::instance().addLine();
|
||||
|
||||
const qint64 fileSize = inputFile.size();
|
||||
QProgressDialog progress(QString("Parsing %1...").arg(fileName), "Cancel", 0, static_cast<int>(fileSize), this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
progress.setMinimumDuration(500);
|
||||
|
||||
QVariantMap rootVars;
|
||||
try {
|
||||
rootVars = mTypeRegistry.parse(rootType, &inputFile, path, [this, &progress](qint64 pos, qint64 size) {
|
||||
progress.setValue(static_cast<int>(pos));
|
||||
QApplication::processEvents();
|
||||
});
|
||||
} catch (const std::exception& e) {
|
||||
LogManager::instance().addLine();
|
||||
LogManager::instance().addError(QString("========== PARSE ERROR in %1 ==========").arg(fileName));
|
||||
LogManager::instance().addError(QString("Error: %1").arg(e.what()));
|
||||
LogManager::instance().addError(QString("File position: 0x%1").arg(inputFile.pos(), 0, 16));
|
||||
LogManager::instance().addLine();
|
||||
QMessageBox::critical(this, "Parse Error",
|
||||
QString("Failed to parse %1:\n\n%2").arg(fileName).arg(e.what()));
|
||||
continue;
|
||||
}
|
||||
|
||||
LogManager::instance().addLine();
|
||||
LogManager::instance().addEntry(QString("========== FINISHED %1 ==========").arg(fileName));
|
||||
LogManager::instance().addLine();
|
||||
|
||||
if (!mOpenedFilePaths.contains(path)) {
|
||||
mOpenedFilePaths.append(path);
|
||||
}
|
||||
|
||||
XTreeWidgetItem* cat = ensureTypeCategoryRoot(rootType);
|
||||
auto* rootInst = addInstanceNode(cat, fileName, rootType, rootVars);
|
||||
routeNestedObjects(rootInst, rootVars);
|
||||
|
||||
cat->setExpanded(true);
|
||||
mTreeWidget->setCurrentItem(rootInst);
|
||||
}
|
||||
});
|
||||
|
||||
connect(actionOpenFolder, &QAction::triggered, this, []() {
|
||||
|
||||
@ -39,6 +39,46 @@ QStringList TypeRegistry::typeNames() const {
|
||||
return m_module.types.keys();
|
||||
}
|
||||
|
||||
QMap<QString, QString> TypeRegistry::supportedExtensions() const {
|
||||
QMap<QString, QString> result;
|
||||
|
||||
for (auto it = m_module.types.begin(); it != m_module.types.end(); ++it) {
|
||||
const TypeDef& td = it.value();
|
||||
if (!td.isRoot) continue;
|
||||
|
||||
// Scan criteria for `require _ext == "xxx"` patterns
|
||||
for (const auto& stmtPtr : td.criteria) {
|
||||
if (!std::holds_alternative<Stmt::Require>(stmtPtr->node)) continue;
|
||||
|
||||
const auto& req = std::get<Stmt::Require>(stmtPtr->node);
|
||||
if (!std::holds_alternative<Expr::Binary>(req.cond->node)) continue;
|
||||
|
||||
const auto& bin = std::get<Expr::Binary>(req.cond->node);
|
||||
if (bin.op != "==") continue;
|
||||
|
||||
// Check for _ext == "xxx" or "xxx" == _ext
|
||||
QString ext;
|
||||
bool lhsIsExt = std::holds_alternative<Expr::Var>(bin.lhs->node) &&
|
||||
std::get<Expr::Var>(bin.lhs->node).name == "_ext";
|
||||
bool rhsIsExt = std::holds_alternative<Expr::Var>(bin.rhs->node) &&
|
||||
std::get<Expr::Var>(bin.rhs->node).name == "_ext";
|
||||
|
||||
if (lhsIsExt && std::holds_alternative<Expr::String>(bin.rhs->node)) {
|
||||
ext = std::get<Expr::String>(bin.rhs->node).v;
|
||||
} else if (rhsIsExt && std::holds_alternative<Expr::String>(bin.lhs->node)) {
|
||||
ext = std::get<Expr::String>(bin.lhs->node).v;
|
||||
}
|
||||
|
||||
if (!ext.isEmpty()) {
|
||||
QString displayName = td.display.isEmpty() ? td.name : td.display;
|
||||
result.insert(ext.toLower(), displayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void TypeRegistry::collectTypeRefsFromExpr(const Expr& expr, QSet<QString>& refs) const {
|
||||
if (std::holds_alternative<Expr::Call>(expr.node)) {
|
||||
const auto& call = std::get<Expr::Call>(expr.node);
|
||||
|
||||
@ -19,6 +19,9 @@ public:
|
||||
bool hasType(const QString& typeName) const;
|
||||
QStringList typeNames() const;
|
||||
|
||||
// Returns map of extension -> display name for all root types
|
||||
QMap<QString, QString> supportedExtensions() const;
|
||||
|
||||
// Validate all type references - returns list of errors, empty if valid
|
||||
QStringList validateTypeReferences() const;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user