feat: update qwfassoc
This commit is contained in:
48
example/qwfassoc/qwfassoc-standalone/CMakeLists.txt
Normal file
48
example/qwfassoc/qwfassoc-standalone/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
||||
# qwfassoc-standalone: executable that uses the qwfassoc library to reproduce
|
||||
# the original tabbed wfassoc configurator.
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(QWFASSOC_STANDALONE_SOURCES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.cpp"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.cpp"
|
||||
)
|
||||
|
||||
set(QWFASSOC_STANDALONE_HEADERS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.h"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.h"
|
||||
)
|
||||
|
||||
set(QWFASSOC_STANDALONE_UI
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.ui"
|
||||
)
|
||||
|
||||
add_executable(qwfassoc-standalone WIN32
|
||||
${QWFASSOC_STANDALONE_SOURCES}
|
||||
${QWFASSOC_STANDALONE_HEADERS}
|
||||
${QWFASSOC_STANDALONE_UI}
|
||||
)
|
||||
|
||||
target_include_directories(qwfassoc-standalone PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
)
|
||||
|
||||
target_link_libraries(qwfassoc-standalone PRIVATE
|
||||
qwfassoc
|
||||
Qt6::Widgets
|
||||
toml11::toml11
|
||||
)
|
||||
|
||||
# Translation pipeline for the standalone executable. The library's strings
|
||||
# are translated by qwfassoc's own .ts file; this one only covers the
|
||||
# executable-specific messages (CLI errors, tab titles, etc.).
|
||||
set(QWFASSOC_STANDALONE_TS_FILES
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc-standalone_zh_CN.ts"
|
||||
)
|
||||
|
||||
qt6_add_translations(qwfassoc-standalone
|
||||
TS_FILES ${QWFASSOC_STANDALONE_TS_FILES}
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_CN">
|
||||
</TS>
|
||||
156
example/qwfassoc/qwfassoc-standalone/src/main.cpp
Normal file
156
example/qwfassoc/qwfassoc-standalone/src/main.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <QApplication>
|
||||
#include <QCommandLineOption>
|
||||
#include <QCommandLineParser>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QTranslator>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <wfassoc++.h>
|
||||
|
||||
#include "main_window.h"
|
||||
#include "manifest_parser.h"
|
||||
#include "scope.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Context used for translatable strings that live outside of any QObject.
|
||||
constexpr const char* kTranslationContext = "qwfassoc-standalone";
|
||||
|
||||
// Show a modal error dialog with the given message and return a non-zero
|
||||
// exit code. Used for the various fatal conditions that may occur before the
|
||||
// main dialog can be shown.
|
||||
int fatal(QWidget* parent, const QString& message) {
|
||||
QMessageBox::critical(parent, QApplication::applicationName(), message);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Convert the --for command line value to a TargetScope. Throws if the value
|
||||
// is not one of the accepted strings.
|
||||
qwfassoc::TargetScope parseScope(const QString& value) {
|
||||
const QString normalized = value.trimmed().toLower();
|
||||
if (normalized == QStringLiteral("user")) {
|
||||
return qwfassoc::TargetScope::User;
|
||||
}
|
||||
if (normalized == QStringLiteral("system")) {
|
||||
return qwfassoc::TargetScope::System;
|
||||
}
|
||||
throw std::runtime_error(
|
||||
"Invalid value for --for. Use \"user\" or \"system\".");
|
||||
}
|
||||
|
||||
// Install the translation(s) matching the user's preferred UI language, if
|
||||
// any. Both the qwfassoc library's .qm and this executable's .qm are loaded
|
||||
// (their .ts files live under the per-project i18n/ directories and are
|
||||
// embedded under the ":/i18n" resource prefix by qt6_add_translations).
|
||||
void installTranslators(QApplication& app) {
|
||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||
for (const QString& locale : uiLanguages) {
|
||||
const QString name = QLocale(locale).name();
|
||||
|
||||
QTranslator* libTranslator = new QTranslator(&app);
|
||||
if (libTranslator->load(QStringLiteral(":/i18n/qwfassoc_") + name)) {
|
||||
app.installTranslator(libTranslator);
|
||||
}
|
||||
|
||||
QTranslator* appTranslator = new QTranslator(&app);
|
||||
if (appTranslator->load(
|
||||
QStringLiteral(":/i18n/qwfassoc-standalone_") + name)) {
|
||||
app.installTranslator(appTranslator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setApplicationName(QStringLiteral("qwfassoc-standalone"));
|
||||
|
||||
// Install available translations before any translatable string is
|
||||
// resolved so that tr() and QCoreApplication::translate() pick up the
|
||||
// right language.
|
||||
installTranslators(app);
|
||||
|
||||
QApplication::setApplicationDisplayName(
|
||||
QCoreApplication::translate(kTranslationContext,
|
||||
"qwfassoc - wfassoc Configurator"));
|
||||
|
||||
// Parse command line arguments using Qt's built-in parser.
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(
|
||||
QCoreApplication::translate(
|
||||
kTranslationContext,
|
||||
"Qt-based GUI executable for the wfassoc library."));
|
||||
parser.addHelpOption();
|
||||
|
||||
QCommandLineOption manifestOption(
|
||||
QStringList() << QStringLiteral("c") << QStringLiteral("manifest"),
|
||||
QCoreApplication::translate(kTranslationContext,
|
||||
"Path to the application manifest TOML file."),
|
||||
QStringLiteral("manifest"));
|
||||
QCommandLineOption forOption(
|
||||
QStringList() << QStringLiteral("f") << QStringLiteral("for"),
|
||||
QCoreApplication::translate(kTranslationContext,
|
||||
"Target scope: \"user\" or \"system\"."),
|
||||
QStringLiteral("scope"));
|
||||
|
||||
parser.addOption(manifestOption);
|
||||
parser.addOption(forOption);
|
||||
parser.process(app);
|
||||
|
||||
// Validate that both mandatory options were provided with sane values.
|
||||
const QString manifestPath = parser.value(manifestOption);
|
||||
const QString forValue = parser.value(forOption);
|
||||
|
||||
if (manifestPath.isEmpty()) {
|
||||
return fatal(nullptr,
|
||||
QCoreApplication::translate(
|
||||
kTranslationContext,
|
||||
"The --manifest/-c option is required."));
|
||||
}
|
||||
if (forValue.isEmpty()) {
|
||||
return fatal(nullptr,
|
||||
QCoreApplication::translate(
|
||||
kTranslationContext,
|
||||
"The --for/-f option is required."));
|
||||
}
|
||||
|
||||
qwfassoc::TargetScope scope;
|
||||
try {
|
||||
scope = parseScope(forValue);
|
||||
} catch (const std::exception& e) {
|
||||
return fatal(nullptr, QString::fromUtf8(e.what()));
|
||||
}
|
||||
|
||||
// Initialize the wfassoc runtime. WFStartup must run before most other
|
||||
// wfassoc calls; if it fails we cannot proceed.
|
||||
if (!wfassoc::WFStartup()) {
|
||||
return fatal(nullptr,
|
||||
QString::fromUtf8(wfassoc::WFGetLastError()));
|
||||
}
|
||||
|
||||
// Build the manifest -> schema -> program pipeline and run the dialog.
|
||||
// Program construction consumes the schema (move) and performs the deep
|
||||
// validation (identifier format, dangling references, etc.).
|
||||
int exitCode = 0;
|
||||
try {
|
||||
qwfassoc::Manifest manifest =
|
||||
qwfassoc::parseManifestFile(manifestPath.toStdString());
|
||||
wfassocpp::Schema schema = qwfassoc::buildSchema(manifest);
|
||||
wfassocpp::Program program(std::move(schema));
|
||||
|
||||
qwfassoc::MainWindow window(std::move(program), scope);
|
||||
exitCode = window.exec();
|
||||
} catch (const std::exception& e) {
|
||||
wfassoc::WFShutdown();
|
||||
return fatal(nullptr, QString::fromUtf8(e.what()));
|
||||
}
|
||||
|
||||
wfassoc::WFShutdown();
|
||||
return exitCode;
|
||||
}
|
||||
79
example/qwfassoc/qwfassoc-standalone/src/main_window.cpp
Normal file
79
example/qwfassoc/qwfassoc-standalone/src/main_window.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "main_window.h"
|
||||
#include "ui_main_window.h"
|
||||
|
||||
#include "application_widget.h"
|
||||
#include "association_widget.h"
|
||||
#include "icon_utils.h"
|
||||
|
||||
#include <QTabWidget>
|
||||
|
||||
namespace qwfassoc {
|
||||
|
||||
MainWindow::MainWindow(wfassocpp::Program program,
|
||||
TargetScope scope,
|
||||
QWidget* parent)
|
||||
: QDialog(parent),
|
||||
ui_(new Ui::MainWindow),
|
||||
appTab_(nullptr),
|
||||
assocTab_(nullptr),
|
||||
program_(std::move(program)),
|
||||
scope_(scope) {
|
||||
ui_->setupUi(this);
|
||||
|
||||
// Resolve program metadata that several labels depend on.
|
||||
programName_ = QString::fromUtf8(program_.ResolveName());
|
||||
|
||||
{
|
||||
auto iconRc = program_.ResolveIcon();
|
||||
auto handle = iconRc.GetIcon();
|
||||
programIcon_ = icon_utils::fromHicon(handle);
|
||||
}
|
||||
|
||||
// Compose the window title and icon.
|
||||
setWindowTitle(tr("%1 Options").arg(programName_));
|
||||
if (!programIcon_.isNull()) {
|
||||
setWindowIcon(QIcon(programIcon_));
|
||||
}
|
||||
|
||||
// Build the two tab pages from the library widgets.
|
||||
appTab_ = new ApplicationWidget(this);
|
||||
assocTab_ = new AssociationWidget(this);
|
||||
|
||||
ui_->tabWidget->addTab(appTab_, tr("Applications"));
|
||||
ui_->tabWidget->addTab(assocTab_, tr("File Associations"));
|
||||
|
||||
// Two-phase initialization. The standalone executable wants the OK and
|
||||
// Cancel buttons visible because they drive dialog acceptance.
|
||||
appTab_->setConfig({&program_, scope_});
|
||||
assocTab_->setConfig({&program_, scope_, /*showOkCancelButtons=*/true});
|
||||
|
||||
// Wire widget signals so that any wfassoc change refreshes both pages,
|
||||
// and the association widget can request dialog closure.
|
||||
connect(appTab_, &ApplicationWidget::changed, this,
|
||||
&MainWindow::onAnyChanged);
|
||||
connect(assocTab_, &AssociationWidget::changed, this,
|
||||
&MainWindow::onAnyChanged);
|
||||
connect(assocTab_, &AssociationWidget::finished, this,
|
||||
&MainWindow::onFinished);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
void MainWindow::onAnyChanged() {
|
||||
if (appTab_ != nullptr) {
|
||||
appTab_->refresh();
|
||||
}
|
||||
if (assocTab_ != nullptr) {
|
||||
assocTab_->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onFinished(bool accepted) {
|
||||
if (accepted) {
|
||||
accept();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace qwfassoc
|
||||
57
example/qwfassoc/qwfassoc-standalone/src/main_window.h
Normal file
57
example/qwfassoc/qwfassoc-standalone/src/main_window.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#ifndef QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||
#define QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
#include <wfassoc++.h>
|
||||
|
||||
#include "scope.h"
|
||||
|
||||
namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
namespace qwfassoc {
|
||||
class ApplicationWidget;
|
||||
class AssociationWidget;
|
||||
}
|
||||
|
||||
namespace qwfassoc {
|
||||
|
||||
// Top-level dialog used by the qwfassoc-standalone executable. Hosts the two
|
||||
// reusable widgets from the qwfassoc library inside a QTabWidget and wires
|
||||
// their changed() / finished() signals together.
|
||||
class MainWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(wfassocpp::Program program,
|
||||
TargetScope scope,
|
||||
QWidget* parent = nullptr);
|
||||
~MainWindow() override;
|
||||
|
||||
private slots:
|
||||
// Called whenever one of the embedded widgets reports that wfassoc state
|
||||
// has changed. Refreshes both widgets so they stay in sync.
|
||||
void onAnyChanged();
|
||||
// Called when the association widget asks the dialog to close.
|
||||
void onFinished(bool accepted);
|
||||
|
||||
private:
|
||||
Ui::MainWindow* ui_;
|
||||
ApplicationWidget* appTab_;
|
||||
AssociationWidget* assocTab_;
|
||||
|
||||
wfassocpp::Program program_;
|
||||
TargetScope scope_;
|
||||
|
||||
QString programName_;
|
||||
QPixmap programIcon_;
|
||||
};
|
||||
|
||||
} // namespace qwfassoc
|
||||
|
||||
#endif // QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||
52
example/qwfassoc/qwfassoc-standalone/src/main_window.ui
Normal file
52
example/qwfassoc/qwfassoc-standalone/src/main_window.ui
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QDialog" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>600</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>600</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mainLayout">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
144
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.cpp
Normal file
144
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include "manifest_parser.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <toml.hpp>
|
||||
|
||||
namespace qwfassoc {
|
||||
|
||||
// region: TOML Parsing
|
||||
|
||||
Manifest parseManifestFile(const std::string& path) {
|
||||
toml::value root;
|
||||
try {
|
||||
root = toml::parse(path);
|
||||
} catch (const std::exception& e) {
|
||||
// toml::parse already produces a descriptive message including file
|
||||
// path and line number; just propagate it wrapped for context.
|
||||
throw std::runtime_error(
|
||||
std::string("Failed to parse manifest TOML file: ") + e.what());
|
||||
}
|
||||
|
||||
Manifest manifest;
|
||||
|
||||
// Helper lambda: read a string field, re-throwing with a clearer message.
|
||||
auto readString = [](const toml::value& v,
|
||||
const std::string& key) -> std::string {
|
||||
try {
|
||||
return toml::find<std::string>(v, key);
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(
|
||||
"Manifest field \"" + key +
|
||||
"\" is missing or is not a string: " + e.what());
|
||||
}
|
||||
};
|
||||
|
||||
// Required top-level scalar fields.
|
||||
manifest.identifier = readString(root, "identifier");
|
||||
manifest.path = readString(root, "path");
|
||||
manifest.clsid = readString(root, "clsid");
|
||||
|
||||
// Optional top-level scalar fields.
|
||||
if (root.contains("name")) {
|
||||
manifest.name = readString(root, "name");
|
||||
}
|
||||
if (root.contains("icon")) {
|
||||
manifest.icon = readString(root, "icon");
|
||||
}
|
||||
if (root.contains("behavior")) {
|
||||
manifest.behavior = readString(root, "behavior");
|
||||
}
|
||||
|
||||
// Helper lambda: copy a TOML table of {string -> string} into a std::map.
|
||||
auto readStringTable =
|
||||
[](const toml::value& parent,
|
||||
const std::string& key) -> std::map<std::string, std::string> {
|
||||
if (!parent.contains(key)) {
|
||||
return {};
|
||||
}
|
||||
std::map<std::string, std::string> out;
|
||||
try {
|
||||
// Keep the sub-value alive as a local so that the table reference
|
||||
// obtained from as_table() stays valid for the loop below,
|
||||
// regardless of whether toml::find returns by reference or by
|
||||
// value in the toml11 version that is linked.
|
||||
const toml::value& sub = toml::find(parent, key);
|
||||
for (const auto& [k, v] : sub.as_table()) {
|
||||
out.emplace(k, v.as_string());
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(
|
||||
"Manifest table \"" + key +
|
||||
"\" contains an invalid entry: " + e.what());
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
manifest.strs = readStringTable(root, "strs");
|
||||
manifest.icons = readStringTable(root, "icons");
|
||||
manifest.behaviors = readStringTable(root, "behaviors");
|
||||
|
||||
// Extension table. Each entry is itself a table with name/icon/behavior.
|
||||
if (root.contains("exts")) {
|
||||
try {
|
||||
const toml::value& exts_value = toml::find(root, "exts");
|
||||
for (const auto& [ext_key, ext_value] : exts_value.as_table()) {
|
||||
ManifestExt ext;
|
||||
ext.name = readString(ext_value, "name");
|
||||
ext.icon = readString(ext_value, "icon");
|
||||
ext.behavior = readString(ext_value, "behavior");
|
||||
manifest.exts.emplace(ext_key, std::move(ext));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
throw std::runtime_error(
|
||||
"Manifest \"exts\" table contains an invalid entry: " +
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region: Schema Conversion
|
||||
|
||||
wfassocpp::Schema buildSchema(const Manifest& manifest) {
|
||||
wfassocpp::Schema schema;
|
||||
|
||||
// The wfassocpp wrappers translate any underlying failure into a
|
||||
// std::runtime_error via _Check, so we let those propagate untouched.
|
||||
schema.SetIdentifier(manifest.identifier.c_str());
|
||||
schema.SetPath(manifest.path.c_str());
|
||||
schema.SetClsid(manifest.clsid.c_str());
|
||||
|
||||
// Optional fields: passing nullptr tells wfassoc to clear the value.
|
||||
schema.SetName(manifest.name.has_value() ? manifest.name->c_str()
|
||||
: nullptr);
|
||||
schema.SetIcon(manifest.icon.has_value() ? manifest.icon->c_str()
|
||||
: nullptr);
|
||||
schema.SetBehavior(manifest.behavior.has_value() ? manifest.behavior->c_str()
|
||||
: nullptr);
|
||||
|
||||
for (const auto& [key, value] : manifest.strs) {
|
||||
schema.AddStr(key.c_str(), value.c_str());
|
||||
}
|
||||
for (const auto& [key, value] : manifest.icons) {
|
||||
schema.AddIcon(key.c_str(), value.c_str());
|
||||
}
|
||||
for (const auto& [key, value] : manifest.behaviors) {
|
||||
schema.AddBehavior(key.c_str(), value.c_str());
|
||||
}
|
||||
for (const auto& [key, value] : manifest.exts) {
|
||||
schema.AddExt(key.c_str(),
|
||||
value.name.c_str(),
|
||||
value.icon.c_str(),
|
||||
value.behavior.c_str());
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
} // namespace qwfassoc
|
||||
25
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.h
Normal file
25
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#ifndef QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||
#define QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <wfassoc++.h>
|
||||
|
||||
#include "manifest.h"
|
||||
|
||||
namespace qwfassoc {
|
||||
|
||||
// Parse a manifest TOML file from disk into a Manifest value.
|
||||
// Throws std::runtime_error on any IO or TOML syntax error.
|
||||
Manifest parseManifestFile(const std::string& path);
|
||||
|
||||
// Build a wfassocpp::Schema from a manifest value.
|
||||
// Throws std::runtime_error (originating from wfassocpp::_Check) when the
|
||||
// wfassoc library rejects an operation, e.g. on duplicate keys or dangling
|
||||
// references.
|
||||
wfassocpp::Schema buildSchema(const Manifest& manifest);
|
||||
|
||||
} // namespace qwfassoc
|
||||
|
||||
#endif // QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||
Reference in New Issue
Block a user