@dusted/anqst 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -123
- package/dist/src/app.js +328 -79
- package/dist/src/backend/ast/emit.js +5 -0
- package/dist/src/backend/ast/index.js +13 -0
- package/dist/src/backend/ast/parser.js +5 -0
- package/dist/src/backend/ast/verify.js +5 -0
- package/dist/src/backend/index.js +16 -0
- package/dist/src/backend/tsc/debug-dump.js +39 -0
- package/dist/src/backend/tsc/emit-cpp.js +13 -0
- package/dist/src/backend/tsc/emit-node.js +13 -0
- package/dist/src/backend/tsc/index.js +41 -0
- package/dist/src/backend/tsc/parser.js +19 -0
- package/dist/src/backend/tsc/program.js +120 -0
- package/dist/src/backend/tsc/typegraph.js +172 -0
- package/dist/src/backend/tsc/verify.js +13 -0
- package/dist/src/backend/types.js +2 -0
- package/dist/src/bin/anqst.js +0 -0
- package/dist/src/emit.js +456 -8
- package/dist/src/project.js +110 -40
- package/package.json +7 -4
package/dist/src/emit.js
CHANGED
|
@@ -8,9 +8,11 @@ exports.writeGeneratedOutputs = writeGeneratedOutputs;
|
|
|
8
8
|
exports.installTypeScriptOutputs = installTypeScriptOutputs;
|
|
9
9
|
exports.installEmbeddedWebBundle = installEmbeddedWebBundle;
|
|
10
10
|
exports.installQtIntegrationCMake = installQtIntegrationCMake;
|
|
11
|
+
exports.installQtDesignerPluginCMake = installQtDesignerPluginCMake;
|
|
11
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
14
|
const typescript_1 = __importDefault(require("typescript"));
|
|
15
|
+
const pngjs_1 = require("pngjs");
|
|
14
16
|
function stripAnQstType(typeText) {
|
|
15
17
|
return typeText
|
|
16
18
|
.replace(/\bAnQst\.Type\.stringArray\b/g, "string[]")
|
|
@@ -158,6 +160,87 @@ function cppToVariantExpression(cppType, expr) {
|
|
|
158
160
|
}
|
|
159
161
|
return `QVariant::fromValue(${expr})`;
|
|
160
162
|
}
|
|
163
|
+
function splitTopLevelTemplateArgs(text) {
|
|
164
|
+
const args = [];
|
|
165
|
+
let start = 0;
|
|
166
|
+
let depth = 0;
|
|
167
|
+
for (let i = 0; i < text.length; i++) {
|
|
168
|
+
const ch = text[i];
|
|
169
|
+
if (ch === "<") {
|
|
170
|
+
depth++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (ch === ">") {
|
|
174
|
+
depth--;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (ch === "," && depth === 0) {
|
|
178
|
+
args.push(text.slice(start, i).trim());
|
|
179
|
+
start = i + 1;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const tail = text.slice(start).trim();
|
|
183
|
+
if (tail.length > 0) {
|
|
184
|
+
args.push(tail);
|
|
185
|
+
}
|
|
186
|
+
return args;
|
|
187
|
+
}
|
|
188
|
+
function templateTypeArgs(cppType, containerName) {
|
|
189
|
+
const trimmed = cppType.trim();
|
|
190
|
+
const prefix = `${containerName}<`;
|
|
191
|
+
if (!trimmed.startsWith(prefix) || !trimmed.endsWith(">")) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const inner = trimmed.slice(prefix.length, -1).trim();
|
|
195
|
+
if (inner.length === 0)
|
|
196
|
+
return [];
|
|
197
|
+
return splitTopLevelTemplateArgs(inner);
|
|
198
|
+
}
|
|
199
|
+
function isNumericCppType(cppType) {
|
|
200
|
+
return [
|
|
201
|
+
"double",
|
|
202
|
+
"qint64",
|
|
203
|
+
"quint64",
|
|
204
|
+
"qint32",
|
|
205
|
+
"quint32",
|
|
206
|
+
"qint16",
|
|
207
|
+
"quint16",
|
|
208
|
+
"qint8",
|
|
209
|
+
"quint8",
|
|
210
|
+
"int8_t",
|
|
211
|
+
"uint8_t",
|
|
212
|
+
"int16_t",
|
|
213
|
+
"uint16_t",
|
|
214
|
+
"int32_t",
|
|
215
|
+
"uint32_t"
|
|
216
|
+
].includes(cppType);
|
|
217
|
+
}
|
|
218
|
+
function designerPlaceholderCppExpression(cppType, memberName) {
|
|
219
|
+
const escapedMember = escapeCppStringLiteral(memberName);
|
|
220
|
+
const stringLiteral = `QStringLiteral("${escapedMember} value")`;
|
|
221
|
+
if (cppType === "QString")
|
|
222
|
+
return stringLiteral;
|
|
223
|
+
if (cppType === "bool")
|
|
224
|
+
return "true";
|
|
225
|
+
if (isNumericCppType(cppType))
|
|
226
|
+
return `static_cast<${cppType}>(1)`;
|
|
227
|
+
if (cppType === "QStringList")
|
|
228
|
+
return `QStringList{${stringLiteral}}`;
|
|
229
|
+
if (cppType === "QVariantMap") {
|
|
230
|
+
return `QVariantMap{{QStringLiteral("${escapedMember}"), QVariant(${stringLiteral})}}`;
|
|
231
|
+
}
|
|
232
|
+
const optionalArgs = templateTypeArgs(cppType, "std::optional");
|
|
233
|
+
if (optionalArgs && optionalArgs.length === 1) {
|
|
234
|
+
const inner = optionalArgs[0];
|
|
235
|
+
return `std::optional<${inner}>{${designerPlaceholderCppExpression(inner, memberName)}}`;
|
|
236
|
+
}
|
|
237
|
+
const listArgs = templateTypeArgs(cppType, "QList");
|
|
238
|
+
if (listArgs && listArgs.length === 1) {
|
|
239
|
+
const inner = listArgs[0];
|
|
240
|
+
return `QList<${inner}>{${designerPlaceholderCppExpression(inner, memberName)}}`;
|
|
241
|
+
}
|
|
242
|
+
return `${cppType}{}`;
|
|
243
|
+
}
|
|
161
244
|
function collectStructDecls(spec) {
|
|
162
245
|
const out = new Map();
|
|
163
246
|
for (const d of spec.namespaceTypeDecls)
|
|
@@ -746,7 +829,12 @@ function renderCppStub(spec, cppTypes) {
|
|
|
746
829
|
const cppType = cppTypes.mapTypeText(member.payloadTypeText, [service.name, member.name, "Payload"]);
|
|
747
830
|
const pascal = pascalCase(member.name);
|
|
748
831
|
lines.push(` if (service == QStringLiteral("${service.name}") && member == QStringLiteral("${member.name}")) {`);
|
|
749
|
-
lines.push(` if (!m_${member.name}Handler)
|
|
832
|
+
lines.push(` if (!m_${member.name}Handler) {`);
|
|
833
|
+
lines.push(` if (property("anqstDesignerContext").toBool()) {`);
|
|
834
|
+
lines.push(` return ${cppToVariantExpression(cppType, designerPlaceholderCppExpression(cppType, member.name))};`);
|
|
835
|
+
lines.push(` }`);
|
|
836
|
+
lines.push(` return QVariant();`);
|
|
837
|
+
lines.push(` }`);
|
|
750
838
|
for (let i = 0; i < member.parameters.length; i++) {
|
|
751
839
|
const p = member.parameters[i];
|
|
752
840
|
const pType = cppTypes.mapTypeText(p.typeText, [service.name, member.name, p.name]);
|
|
@@ -913,6 +1001,57 @@ function escapeXml(text) {
|
|
|
913
1001
|
function normalizeSlashes(value) {
|
|
914
1002
|
return value.split(node_path_1.default.sep).join("/");
|
|
915
1003
|
}
|
|
1004
|
+
function resolveActiveBuildStamp() {
|
|
1005
|
+
const fromEnv = process.env.ANQST_BUILD_STAMP?.trim();
|
|
1006
|
+
if (fromEnv && fromEnv.length > 0) {
|
|
1007
|
+
return fromEnv;
|
|
1008
|
+
}
|
|
1009
|
+
const activePath = node_path_1.default.resolve(__dirname, "..", "..", ".anqstgen-version-active.json");
|
|
1010
|
+
if (!node_fs_1.default.existsSync(activePath)) {
|
|
1011
|
+
return "";
|
|
1012
|
+
}
|
|
1013
|
+
try {
|
|
1014
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(activePath, "utf8"));
|
|
1015
|
+
if (typeof parsed.active === "string" && parsed.active.trim().length > 0) {
|
|
1016
|
+
return parsed.active.trim();
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
catch {
|
|
1020
|
+
return "";
|
|
1021
|
+
}
|
|
1022
|
+
return "";
|
|
1023
|
+
}
|
|
1024
|
+
function withBuildStamp(relativePath, content) {
|
|
1025
|
+
const stamp = resolveActiveBuildStamp();
|
|
1026
|
+
if (!stamp) {
|
|
1027
|
+
return content;
|
|
1028
|
+
}
|
|
1029
|
+
const marker = `Built by AnQst ${stamp}`;
|
|
1030
|
+
const rel = normalizeSlashes(relativePath);
|
|
1031
|
+
const lower = rel.toLowerCase();
|
|
1032
|
+
if (lower.endsWith(".json")) {
|
|
1033
|
+
try {
|
|
1034
|
+
const parsed = JSON.parse(content);
|
|
1035
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1036
|
+
const next = { "//": marker, ...parsed };
|
|
1037
|
+
return `${JSON.stringify(next, null, 2)}\n`;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
catch {
|
|
1041
|
+
// If JSON parsing fails, fall through to plain comment prefix.
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (lower.endsWith(".qrc") || lower.endsWith(".xml") || lower.endsWith(".html")) {
|
|
1045
|
+
return `<!-- ${marker} -->\n${content}`;
|
|
1046
|
+
}
|
|
1047
|
+
if (lower.endsWith(".cmake")) {
|
|
1048
|
+
return `# ${marker}\n${content}`;
|
|
1049
|
+
}
|
|
1050
|
+
if (lower.endsWith(".h") || lower.endsWith(".cpp") || lower.endsWith(".ts") || lower.endsWith(".js") || lower.endsWith(".d.ts")) {
|
|
1051
|
+
return `// ${marker}\n${content}`;
|
|
1052
|
+
}
|
|
1053
|
+
return `# ${marker}\n${content}`;
|
|
1054
|
+
}
|
|
916
1055
|
function renderEmbeddedQrc(widgetName, embeddedWebFiles) {
|
|
917
1056
|
const files = [...embeddedWebFiles].sort();
|
|
918
1057
|
const lines = [];
|
|
@@ -1231,6 +1370,11 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1231
1370
|
}
|
|
1232
1371
|
if (type === "hostError") {
|
|
1233
1372
|
console.error("AnQst host error:", message["payload"]);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
if (type === "widgetReattached") {
|
|
1376
|
+
document.body.textContent = "Widget Reattached";
|
|
1377
|
+
this.socket.close();
|
|
1234
1378
|
}
|
|
1235
1379
|
});
|
|
1236
1380
|
}
|
|
@@ -1240,12 +1384,22 @@ class WebSocketBridgeAdapter implements BridgeAdapter {
|
|
|
1240
1384
|
if (!configResponse.ok) {
|
|
1241
1385
|
throw new Error("AnQst host bootstrap missing: unable to read /anqst-dev-config.json");
|
|
1242
1386
|
}
|
|
1243
|
-
const config = (await configResponse.json()) as { wsUrl?: string };
|
|
1244
|
-
|
|
1245
|
-
|
|
1387
|
+
const config = (await configResponse.json()) as { wsUrl?: string; wsPath?: string };
|
|
1388
|
+
let wsUrl = config.wsUrl;
|
|
1389
|
+
if (!wsUrl && config.wsPath) {
|
|
1390
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
1391
|
+
wsUrl = protocol + "//" + window.location.host + config.wsPath;
|
|
1392
|
+
}
|
|
1393
|
+
if (!wsUrl) {
|
|
1394
|
+
throw new Error("AnQst host bootstrap missing: wsUrl/wsPath is unavailable.");
|
|
1395
|
+
}
|
|
1396
|
+
if (wsUrl.startsWith("http://")) {
|
|
1397
|
+
wsUrl = "ws://" + wsUrl.slice("http://".length);
|
|
1398
|
+
} else if (wsUrl.startsWith("https://")) {
|
|
1399
|
+
wsUrl = "wss://" + wsUrl.slice("https://".length);
|
|
1246
1400
|
}
|
|
1247
1401
|
return await new Promise<WebSocketBridgeAdapter>((resolve, reject) => {
|
|
1248
|
-
const socket = new WebSocket(
|
|
1402
|
+
const socket = new WebSocket(wsUrl!);
|
|
1249
1403
|
socket.addEventListener("open", () => resolve(new WebSocketBridgeAdapter(socket)));
|
|
1250
1404
|
socket.addEventListener("error", () => reject(new Error("Failed to connect to AnQst WebSocket bridge.")));
|
|
1251
1405
|
});
|
|
@@ -2224,7 +2378,7 @@ function writeGeneratedOutputs(cwd, outputs) {
|
|
|
2224
2378
|
for (const [relPath, content] of Object.entries(outputs)) {
|
|
2225
2379
|
const filePath = node_path_1.default.join(outputRoot, relPath);
|
|
2226
2380
|
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
|
|
2227
|
-
node_fs_1.default.writeFileSync(filePath, content, "utf8");
|
|
2381
|
+
node_fs_1.default.writeFileSync(filePath, withBuildStamp(relPath, content), "utf8");
|
|
2228
2382
|
}
|
|
2229
2383
|
}
|
|
2230
2384
|
function installTypeScriptOutputs(cwd) {
|
|
@@ -2393,7 +2547,7 @@ function installEmbeddedWebBundle(cwd, widgetName) {
|
|
|
2393
2547
|
normalizeEmbeddedIndexHtml(node_path_1.default.join(cppLibraryWebRoot, "index.html"), cppLibraryWebRoot);
|
|
2394
2548
|
const embeddedFiles = listFilesRecursively(cppLibraryWebRoot);
|
|
2395
2549
|
const qrcPath = node_path_1.default.join(cppLibraryRoot, `${widgetName}.qrc`);
|
|
2396
|
-
node_fs_1.default.writeFileSync(qrcPath, renderEmbeddedQrc(widgetName, embeddedFiles), "utf8");
|
|
2550
|
+
node_fs_1.default.writeFileSync(qrcPath, withBuildStamp(`${widgetName}.qrc`, renderEmbeddedQrc(widgetName, embeddedFiles)), "utf8");
|
|
2397
2551
|
return true;
|
|
2398
2552
|
}
|
|
2399
2553
|
function normalizeEmbeddedIndexHtml(indexPath, webRoot) {
|
|
@@ -2496,5 +2650,299 @@ target_link_libraries(${widgetTarget}
|
|
|
2496
2650
|
function installQtIntegrationCMake(cwd, widgetName) {
|
|
2497
2651
|
const integrationDir = node_path_1.default.join(cwd, "anqst-cmake");
|
|
2498
2652
|
node_fs_1.default.mkdirSync(integrationDir, { recursive: true });
|
|
2499
|
-
node_fs_1.default.writeFileSync(node_path_1.default.join(integrationDir, "CMakeLists.txt"), renderQtIntegrationCMake(widgetName), "utf8");
|
|
2653
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(integrationDir, "CMakeLists.txt"), withBuildStamp("anqst-cmake/CMakeLists.txt", renderQtIntegrationCMake(widgetName)), "utf8");
|
|
2654
|
+
}
|
|
2655
|
+
function normalizeIcoSize(dim) {
|
|
2656
|
+
return dim === 0 ? 256 : dim;
|
|
2657
|
+
}
|
|
2658
|
+
function escapeCppStringLiteral(value) {
|
|
2659
|
+
return value
|
|
2660
|
+
.replace(/\\/g, "\\\\")
|
|
2661
|
+
.replace(/"/g, '\\"')
|
|
2662
|
+
.replace(/\r/g, "\\r")
|
|
2663
|
+
.replace(/\n/g, "\\n");
|
|
2664
|
+
}
|
|
2665
|
+
function readDistFavicon(cwd) {
|
|
2666
|
+
const distRoot = node_path_1.default.join(cwd, "dist");
|
|
2667
|
+
if (!node_fs_1.default.existsSync(distRoot) || !node_fs_1.default.statSync(distRoot).isDirectory()) {
|
|
2668
|
+
return null;
|
|
2669
|
+
}
|
|
2670
|
+
const stack = [distRoot];
|
|
2671
|
+
while (stack.length > 0) {
|
|
2672
|
+
const current = stack.shift();
|
|
2673
|
+
const entries = node_fs_1.default.readdirSync(current, { withFileTypes: true })
|
|
2674
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2675
|
+
for (const entry of entries) {
|
|
2676
|
+
const fullPath = node_path_1.default.join(current, entry.name);
|
|
2677
|
+
if (entry.isDirectory()) {
|
|
2678
|
+
stack.push(fullPath);
|
|
2679
|
+
continue;
|
|
2680
|
+
}
|
|
2681
|
+
if (entry.isFile() && entry.name.toLowerCase() === "favicon.ico") {
|
|
2682
|
+
return node_fs_1.default.readFileSync(fullPath);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
return null;
|
|
2687
|
+
}
|
|
2688
|
+
function resolveFaviconIcoBuffer(cwd) {
|
|
2689
|
+
const distFavicon = readDistFavicon(cwd);
|
|
2690
|
+
if (distFavicon !== null) {
|
|
2691
|
+
return distFavicon;
|
|
2692
|
+
}
|
|
2693
|
+
const fallbackFiles = [
|
|
2694
|
+
node_path_1.default.join(cwd, "res", "favicon.ico"),
|
|
2695
|
+
node_path_1.default.join(cwd, "src", "favicon.ico"),
|
|
2696
|
+
node_path_1.default.join(cwd, "favicon.ico")
|
|
2697
|
+
];
|
|
2698
|
+
for (const filePath of fallbackFiles) {
|
|
2699
|
+
if (node_fs_1.default.existsSync(filePath) && node_fs_1.default.statSync(filePath).isFile()) {
|
|
2700
|
+
return node_fs_1.default.readFileSync(filePath);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
function decodeIcoBmpToPng(imageData) {
|
|
2706
|
+
if (imageData.length < 40) {
|
|
2707
|
+
throw new Error("ICO BMP frame too small.");
|
|
2708
|
+
}
|
|
2709
|
+
const headerSize = imageData.readUInt32LE(0);
|
|
2710
|
+
if (headerSize < 40 || imageData.length < headerSize) {
|
|
2711
|
+
throw new Error("ICO BMP frame has unsupported DIB header.");
|
|
2712
|
+
}
|
|
2713
|
+
const width = imageData.readInt32LE(4);
|
|
2714
|
+
const heightTotal = imageData.readInt32LE(8);
|
|
2715
|
+
const planes = imageData.readUInt16LE(12);
|
|
2716
|
+
const bitCount = imageData.readUInt16LE(14);
|
|
2717
|
+
const compression = imageData.readUInt32LE(16);
|
|
2718
|
+
if (width <= 0 || heightTotal <= 0) {
|
|
2719
|
+
throw new Error("ICO BMP frame has invalid dimensions.");
|
|
2720
|
+
}
|
|
2721
|
+
const height = Math.floor(heightTotal / 2);
|
|
2722
|
+
if (height <= 0) {
|
|
2723
|
+
throw new Error("ICO BMP frame has invalid mask height.");
|
|
2724
|
+
}
|
|
2725
|
+
if (planes !== 1 || bitCount !== 32 || compression !== 0) {
|
|
2726
|
+
throw new Error("ICO BMP frame format unsupported; expected 32-bit BI_RGB.");
|
|
2727
|
+
}
|
|
2728
|
+
const pixelOffset = headerSize;
|
|
2729
|
+
const rowBytes = width * 4;
|
|
2730
|
+
const pixelBytes = rowBytes * height;
|
|
2731
|
+
if (imageData.length < pixelOffset + pixelBytes) {
|
|
2732
|
+
throw new Error("ICO BMP frame is truncated.");
|
|
2733
|
+
}
|
|
2734
|
+
const png = new pngjs_1.PNG({ width, height });
|
|
2735
|
+
for (let y = 0; y < height; y += 1) {
|
|
2736
|
+
const srcY = height - 1 - y;
|
|
2737
|
+
const srcRow = pixelOffset + srcY * rowBytes;
|
|
2738
|
+
const dstRow = y * rowBytes;
|
|
2739
|
+
for (let x = 0; x < width; x += 1) {
|
|
2740
|
+
const src = srcRow + x * 4;
|
|
2741
|
+
const dst = dstRow + x * 4;
|
|
2742
|
+
const b = imageData[src];
|
|
2743
|
+
const g = imageData[src + 1];
|
|
2744
|
+
const r = imageData[src + 2];
|
|
2745
|
+
const a = imageData[src + 3];
|
|
2746
|
+
png.data[dst] = r;
|
|
2747
|
+
png.data[dst + 1] = g;
|
|
2748
|
+
png.data[dst + 2] = b;
|
|
2749
|
+
png.data[dst + 3] = a;
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
return pngjs_1.PNG.sync.write(png);
|
|
2753
|
+
}
|
|
2754
|
+
function convertIcoToPngBuffer(icoBytes) {
|
|
2755
|
+
if (icoBytes.length < 6) {
|
|
2756
|
+
throw new Error("favicon.ico is too small.");
|
|
2757
|
+
}
|
|
2758
|
+
const reserved = icoBytes.readUInt16LE(0);
|
|
2759
|
+
const iconType = icoBytes.readUInt16LE(2);
|
|
2760
|
+
const count = icoBytes.readUInt16LE(4);
|
|
2761
|
+
if (reserved !== 0 || iconType !== 1 || count === 0) {
|
|
2762
|
+
throw new Error("favicon.ico has invalid ICO header.");
|
|
2763
|
+
}
|
|
2764
|
+
if (icoBytes.length < 6 + count * 16) {
|
|
2765
|
+
throw new Error("favicon.ico has truncated directory entries.");
|
|
2766
|
+
}
|
|
2767
|
+
const frames = [];
|
|
2768
|
+
for (let i = 0; i < count; i += 1) {
|
|
2769
|
+
const entryOffset = 6 + i * 16;
|
|
2770
|
+
const width = normalizeIcoSize(icoBytes[entryOffset]);
|
|
2771
|
+
const height = normalizeIcoSize(icoBytes[entryOffset + 1]);
|
|
2772
|
+
const bytesInRes = icoBytes.readUInt32LE(entryOffset + 8);
|
|
2773
|
+
const imageOffset = icoBytes.readUInt32LE(entryOffset + 12);
|
|
2774
|
+
if (bytesInRes === 0)
|
|
2775
|
+
continue;
|
|
2776
|
+
if (imageOffset + bytesInRes > icoBytes.length)
|
|
2777
|
+
continue;
|
|
2778
|
+
frames.push({ width, height, bytesInRes, imageOffset });
|
|
2779
|
+
}
|
|
2780
|
+
if (frames.length === 0) {
|
|
2781
|
+
throw new Error("favicon.ico contains no readable image frames.");
|
|
2782
|
+
}
|
|
2783
|
+
frames.sort((a, b) => {
|
|
2784
|
+
const areaDiff = b.width * b.height - a.width * a.height;
|
|
2785
|
+
if (areaDiff !== 0)
|
|
2786
|
+
return areaDiff;
|
|
2787
|
+
return b.bytesInRes - a.bytesInRes;
|
|
2788
|
+
});
|
|
2789
|
+
const pngSignature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
2790
|
+
for (const frame of frames) {
|
|
2791
|
+
const imageData = icoBytes.subarray(frame.imageOffset, frame.imageOffset + frame.bytesInRes);
|
|
2792
|
+
if (imageData.subarray(0, 8).equals(pngSignature)) {
|
|
2793
|
+
return Buffer.from(imageData);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
return decodeIcoBmpToPng(icoBytes.subarray(frames[0].imageOffset, frames[0].imageOffset + frames[0].bytesInRes));
|
|
2797
|
+
}
|
|
2798
|
+
function renderDesignerPluginQrc() {
|
|
2799
|
+
return `<RCC>
|
|
2800
|
+
<qresource prefix="/anqstdesignerplugin">
|
|
2801
|
+
<file>plugin-icon.png</file>
|
|
2802
|
+
</qresource>
|
|
2803
|
+
</RCC>
|
|
2804
|
+
`;
|
|
2805
|
+
}
|
|
2806
|
+
function installDesignerPluginIconAssets(cwd, pluginDir) {
|
|
2807
|
+
const iconTargetPath = node_path_1.default.join(pluginDir, "plugin-icon.png");
|
|
2808
|
+
const qrcTargetPath = node_path_1.default.join(pluginDir, "designerplugin.qrc");
|
|
2809
|
+
const icoBytes = resolveFaviconIcoBuffer(cwd);
|
|
2810
|
+
if (icoBytes === null) {
|
|
2811
|
+
if (node_fs_1.default.existsSync(iconTargetPath))
|
|
2812
|
+
node_fs_1.default.rmSync(iconTargetPath, { force: true });
|
|
2813
|
+
if (node_fs_1.default.existsSync(qrcTargetPath))
|
|
2814
|
+
node_fs_1.default.rmSync(qrcTargetPath, { force: true });
|
|
2815
|
+
return { hasIcon: false };
|
|
2816
|
+
}
|
|
2817
|
+
const pngBytes = convertIcoToPngBuffer(icoBytes);
|
|
2818
|
+
node_fs_1.default.writeFileSync(iconTargetPath, pngBytes);
|
|
2819
|
+
node_fs_1.default.writeFileSync(qrcTargetPath, renderDesignerPluginQrc(), "utf8");
|
|
2820
|
+
return { hasIcon: true };
|
|
2821
|
+
}
|
|
2822
|
+
function renderQtDesignerPluginCpp(widgetName, widgetCategory, hasIcon) {
|
|
2823
|
+
const pluginClass = `${widgetName}DesignerPlugin`;
|
|
2824
|
+
const widgetClass = `${widgetName}::${widgetName}`;
|
|
2825
|
+
const groupName = escapeCppStringLiteral(widgetCategory);
|
|
2826
|
+
const iconExpression = hasIcon
|
|
2827
|
+
? 'QIcon(QStringLiteral(":/anqstdesignerplugin/plugin-icon.png"))'
|
|
2828
|
+
: "QIcon()";
|
|
2829
|
+
return `#include <QtUiPlugin/QDesignerCustomWidgetInterface>
|
|
2830
|
+
#include <QIcon>
|
|
2831
|
+
#include <QObject>
|
|
2832
|
+
#include <QString>
|
|
2833
|
+
#include <QWidget>
|
|
2834
|
+
#include "include/${widgetName}.h"
|
|
2835
|
+
|
|
2836
|
+
class ${pluginClass} final : public QObject, public QDesignerCustomWidgetInterface {
|
|
2837
|
+
Q_OBJECT
|
|
2838
|
+
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface")
|
|
2839
|
+
Q_INTERFACES(QDesignerCustomWidgetInterface)
|
|
2840
|
+
|
|
2841
|
+
public:
|
|
2842
|
+
explicit ${pluginClass}(QObject* parent = nullptr) : QObject(parent) {}
|
|
2843
|
+
|
|
2844
|
+
QString name() const override { return QStringLiteral("${widgetClass}"); }
|
|
2845
|
+
QString group() const override { return QStringLiteral("${groupName}"); }
|
|
2846
|
+
QIcon icon() const override { return ${iconExpression}; }
|
|
2847
|
+
QString toolTip() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
|
|
2848
|
+
QString whatsThis() const override { return QStringLiteral("${widgetName} generated by AnQst."); }
|
|
2849
|
+
bool isContainer() const override { return false; }
|
|
2850
|
+
QString includeFile() const override { return QStringLiteral("include/${widgetName}.h"); }
|
|
2851
|
+
QWidget* createWidget(QWidget* parent) override {
|
|
2852
|
+
auto* widget = new ${widgetClass}(parent);
|
|
2853
|
+
widget->setProperty("anqstDesignerContext", true);
|
|
2854
|
+
return widget;
|
|
2855
|
+
}
|
|
2856
|
+
bool isInitialized() const override { return true; }
|
|
2857
|
+
void initialize(QDesignerFormEditorInterface*) override {}
|
|
2858
|
+
|
|
2859
|
+
QString domXml() const override {
|
|
2860
|
+
return QStringLiteral(
|
|
2861
|
+
"<ui language=\\"c++\\">\\n"
|
|
2862
|
+
" <widget class=\\"${widgetClass}\\" name=\\"${widgetName.toLowerCase()}\\">\\n"
|
|
2863
|
+
" </widget>\\n"
|
|
2864
|
+
"</ui>\\n");
|
|
2865
|
+
}
|
|
2866
|
+
};
|
|
2867
|
+
|
|
2868
|
+
#include "${pluginClass}.moc"
|
|
2869
|
+
`;
|
|
2870
|
+
}
|
|
2871
|
+
function renderQtDesignerPluginCMake(widgetName, hasIcon) {
|
|
2872
|
+
const widgetTarget = `${widgetName}Widget`;
|
|
2873
|
+
const pluginTarget = `${widgetName}DesignerPlugin`;
|
|
2874
|
+
const resourceLine = hasIcon ? " \"${CMAKE_CURRENT_LIST_DIR}/designerplugin.qrc\"\n" : "";
|
|
2875
|
+
return `cmake_minimum_required(VERSION 3.21)
|
|
2876
|
+
project(${pluginTarget} LANGUAGES CXX)
|
|
2877
|
+
|
|
2878
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
2879
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
2880
|
+
set(CMAKE_AUTOMOC ON)
|
|
2881
|
+
set(CMAKE_AUTOUIC ON)
|
|
2882
|
+
set(CMAKE_AUTORCC ON)
|
|
2883
|
+
|
|
2884
|
+
set(ANQST_WEBAPP_ROOT "\${CMAKE_CURRENT_LIST_DIR}/../..")
|
|
2885
|
+
set(ANQST_WIDGET_DIR "\${ANQST_WEBAPP_ROOT}/generated_output/${generatedCppLibraryDirName(widgetName)}")
|
|
2886
|
+
set(ANQST_WEBBASE_DIR "" CACHE PATH "Path to AnQstWebBase source directory")
|
|
2887
|
+
|
|
2888
|
+
if(NOT EXISTS "\${ANQST_WIDGET_DIR}/CMakeLists.txt")
|
|
2889
|
+
message(FATAL_ERROR "Missing generated widget CMake project at \${ANQST_WIDGET_DIR}. Run 'anqst build' first.")
|
|
2890
|
+
endif()
|
|
2891
|
+
|
|
2892
|
+
if(NOT ANQST_WEBBASE_DIR)
|
|
2893
|
+
foreach(candidate
|
|
2894
|
+
"\${ANQST_WEBAPP_ROOT}/AnQstWidget/AnQstWebBase"
|
|
2895
|
+
"\${ANQST_WEBAPP_ROOT}/../AnQstWidget/AnQstWebBase"
|
|
2896
|
+
"\${ANQST_WEBAPP_ROOT}/../../AnQstWidget/AnQstWebBase"
|
|
2897
|
+
"\${ANQST_WEBAPP_ROOT}/../../../AnQstWidget/AnQstWebBase")
|
|
2898
|
+
if(EXISTS "\${candidate}/CMakeLists.txt")
|
|
2899
|
+
set(ANQST_WEBBASE_DIR "\${candidate}")
|
|
2900
|
+
break()
|
|
2901
|
+
endif()
|
|
2902
|
+
endforeach()
|
|
2903
|
+
endif()
|
|
2904
|
+
|
|
2905
|
+
if(NOT ANQST_WEBBASE_DIR OR NOT EXISTS "\${ANQST_WEBBASE_DIR}/CMakeLists.txt")
|
|
2906
|
+
message(FATAL_ERROR "Unable to locate AnQstWebBase sources. Set -DANQST_WEBBASE_DIR=<path/to/AnQstWidget/AnQstWebBase>.")
|
|
2907
|
+
endif()
|
|
2908
|
+
|
|
2909
|
+
find_package(Qt5 REQUIRED COMPONENTS Core Widgets UiPlugin)
|
|
2910
|
+
|
|
2911
|
+
set(ANQSTWEBBASE_BUILD_TESTS OFF CACHE BOOL "Build AnQstWebBase unit tests" FORCE)
|
|
2912
|
+
if(NOT TARGET anqstwebhostbase)
|
|
2913
|
+
add_subdirectory("\${ANQST_WEBBASE_DIR}" "\${CMAKE_CURRENT_BINARY_DIR}/anqstwebbase")
|
|
2914
|
+
endif()
|
|
2915
|
+
|
|
2916
|
+
if(NOT TARGET ${widgetTarget})
|
|
2917
|
+
add_subdirectory("\${ANQST_WIDGET_DIR}" "\${CMAKE_CURRENT_BINARY_DIR}/generated-widget")
|
|
2918
|
+
endif()
|
|
2919
|
+
|
|
2920
|
+
add_library(${pluginTarget} MODULE
|
|
2921
|
+
"\${CMAKE_CURRENT_LIST_DIR}/${pluginTarget}.cpp"
|
|
2922
|
+
${resourceLine})
|
|
2923
|
+
target_include_directories(${pluginTarget}
|
|
2924
|
+
PRIVATE
|
|
2925
|
+
"\${ANQST_WIDGET_DIR}"
|
|
2926
|
+
"\${ANQST_WIDGET_DIR}/include"
|
|
2927
|
+
)
|
|
2928
|
+
target_link_libraries(${pluginTarget}
|
|
2929
|
+
PRIVATE
|
|
2930
|
+
${widgetTarget}
|
|
2931
|
+
Qt5::Core
|
|
2932
|
+
Qt5::Widgets
|
|
2933
|
+
Qt5::UiPlugin
|
|
2934
|
+
)
|
|
2935
|
+
set_target_properties(${pluginTarget} PROPERTIES
|
|
2936
|
+
PREFIX ""
|
|
2937
|
+
)
|
|
2938
|
+
`;
|
|
2939
|
+
}
|
|
2940
|
+
function installQtDesignerPluginCMake(cwd, widgetName, options = {}) {
|
|
2941
|
+
const pluginDir = node_path_1.default.join(cwd, "anqst-cmake", "designerplugin");
|
|
2942
|
+
node_fs_1.default.mkdirSync(pluginDir, { recursive: true });
|
|
2943
|
+
const assets = installDesignerPluginIconAssets(cwd, pluginDir);
|
|
2944
|
+
const pluginTarget = `${widgetName}DesignerPlugin`;
|
|
2945
|
+
const widgetCategory = options.widgetCategory ?? "AnQst Widgets";
|
|
2946
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, "CMakeLists.txt"), withBuildStamp("anqst-cmake/designerplugin/CMakeLists.txt", renderQtDesignerPluginCMake(widgetName, assets.hasIcon)), "utf8");
|
|
2947
|
+
node_fs_1.default.writeFileSync(node_path_1.default.join(pluginDir, `${pluginTarget}.cpp`), withBuildStamp(`anqst-cmake/designerplugin/${pluginTarget}.cpp`, renderQtDesignerPluginCpp(widgetName, widgetCategory, assets.hasIcon)), "utf8");
|
|
2500
2948
|
}
|