diff --git a/Locales/ja.qm b/Locales/ja.qm index 06df954..6b7b67a 100644 Binary files a/Locales/ja.qm and b/Locales/ja.qm differ diff --git a/Locales/zh.qm b/Locales/zh.qm index 41930fc..321e07c 100644 Binary files a/Locales/zh.qm and b/Locales/zh.qm differ diff --git a/Locales/zh_TW.qm b/Locales/zh_TW.qm index 705aee3..b91b343 100644 Binary files a/Locales/zh_TW.qm and b/Locales/zh_TW.qm differ diff --git a/bin/ADVServer.exe b/bin/ADVServer.exe index 6882649..d1c63eb 100644 Binary files a/bin/ADVServer.exe and b/bin/ADVServer.exe differ diff --git a/bin/advConfig.ini b/bin/advConfig.ini index 5120cea..6c7457a 100644 --- a/bin/advConfig.ini +++ b/bin/advConfig.ini @@ -1,13 +1,13 @@ -[Server] -ip = local -port = 5055 -maxClient = 10 +[server] +port = 5050 +maxclient = 5 logger = true -[FFT] +[fft] attack = 5 decay = 5 -norspeed = 1 peakthr = 10 +norspeed = 10 fps = 35 -changeSpeed = 20 \ No newline at end of file +changespeed = 20 + diff --git a/module.qml b/module.qml index e0ca425..ec52a9f 100644 --- a/module.qml +++ b/module.qml @@ -1,13 +1,15 @@ import NERvGear 1.0 as NVG import NERvGear.Private 1.0 as NVGP +import QtQuick 2.12 +import QtQuick.Controls 2.12 + import "./qml" NVG.Module { initialize: function () { console.log("Initializing ADV-Plugin."); - Common.execute("../bin/ADVServer.exe", "-reboot"); -// Common.setWsocket(true); + Common.execute(Common.serverEXE, "-reboot"); return true; } @@ -18,6 +20,20 @@ NVG.Module { cleanup: function () { console.log("Cleaning up ADV-Plugin."); Common.setWsocket(false); - Common.execute("../bin/ADVServer.exe", "-close"); + Common.execute(Common.serverEXE, "-close"); + } + + Connections { + target: Common + onServerPreferencesOpen: { + serverDialog.active = true; + serverDialog.item.visible = true; + } + } + + Loader { + id: serverDialog + active: false + sourceComponent: ServerPreferences { } } } diff --git a/package.json b/package.json index e15255b..68a3b65 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "top.mashiros.widget.advp", - "version": "1.2.1", - + "version": "1.3.0", + "title": { "en": "ADV Plugin", "zh": "音频可视化插件", @@ -115,7 +115,7 @@ "ja": "オーディナル・スケール- UI 下部" }, "entry": "./styles/Preset_Ordinal_Scale_UI_bottom/Style.qml" - }, + }, { "location": "/preset/advp-style-preset/line", "catalog": "preset/widget", @@ -189,4 +189,4 @@ "entry": "Presets/Ordinal_Scale_UI_bottom/preset.json" } ] -} \ No newline at end of file +} diff --git a/qml/Common.qml b/qml/Common.qml index 9d581a0..d7d5587 100644 --- a/qml/Common.qml +++ b/qml/Common.qml @@ -9,18 +9,41 @@ import NERvGear 1.0 as NVG Item { readonly property var styles: [] readonly property var stylesURL: [] + readonly property var defaultServerCFG: { + "server": { + "port": 5050, + "maxclient": 5, + "logger": true + }, + "fft": { + "attack": 5, + "decay": 5, + "norspeed": 1, + "peakthr": 10, + "fps": 35, + "changespeed": 20 + } + } + readonly property string iniFile: "../bin/advConfig.ini" + readonly property string serverEXE: "../bin/ADVServer.exe" + + property var serverCFG: defaultServerCFG + property string wsIp: "localhost" + property int wsPort: serverCFG.server.port property int widgetsNum: 0 + property bool debug: false + property bool rebootFlag: false signal audioDataUpdated(var audioData) - signal wsocketClosed() + signal serverPreferencesOpen() function execute(path, args) { path = NVG.Url.toLocalFile(Qt.resolvedUrl(path)); NVG.SystemCall.execute(path, args); } - function openFile(fileUrl) { + function readFile(fileUrl) { let request = new XMLHttpRequest(); request.open("GET", fileUrl, false); request.send(null); @@ -29,7 +52,14 @@ Item { return data; } - function parseINIString(data){ + function writeFile(fileUrl, text) { + var request = new XMLHttpRequest(); + request.open("PUT", fileUrl, false); + request.send(text); + return request.status; + } + + function parseINIString(data) { let regex = { section: /\[\s*([^]*)\s*\]\s*$/, param: /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/, @@ -40,56 +70,113 @@ Item { let section = null; let match; lines.forEach(function(line){ - if(regex.comment.test(line)){ - return; - }else if(regex.param.test(line)){ - match = line.match(regex.param); - if(section){ - value[section][match[1]] = match[2]; - }else{ - value[match[1]] = match[2]; - } - }else if(regex.section.test(line)){ - match = line.match(regex.section); - value[match[1]] = {}; - section = match[1]; - }else if(line.length === 0 && section){ - section = null; - }; + if (regex.comment.test(line)) { + return; + } else if (regex.param.test(line)) { + match = line.match(regex.param); + if (["true", "false"].indexOf(match[2]) > -1) { + match[2] = Boolean(1 - ["true", "false"].indexOf(match[2])); + } else if (/^\d+$/.test(match[2])) { + match[2] = Number(match[2]); + } + if (section) { + value[section][match[1]] = match[2]; + } else { + value[match[1]] = match[2]; + } + } else if (regex.section.test(line)) { + match = line.match(regex.section); + value[match[1]] = {}; + section = match[1]; + } else if(line.length === 0 && section) { + section = null; + }; }); return value; } + function convertINIString(data) { + let value = ""; + for (let section in data) { + value += "[" + section + "]\n"; + for (let key in data[section]) { + value += key + " = " + String(data[section][key]) + "\n"; + } + value += "\n"; + } + return value; + } + + function deepClone(obj) { + let objClone = Array.isArray(obj) ? [] : {}; + if (obj && typeof obj === "object" && obj != null) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if (obj[key] && typeof obj[key] === "object") { + objClone[key] = deepClone(obj[key]); + } else { + objClone[key] = obj[key]; + } + } + } + } + return objClone; + } + + function isObjectValueEqual(a, b) { + if (a === b) + return true; + let aProps = Object.getOwnPropertyNames(a); + let bProps = Object.getOwnPropertyNames(b); + if (aProps.length !== bProps.length) + return false; + for (let prop in a) { + if (b.hasOwnProperty(prop)) { + if (typeof a[prop] === 'object') { + if (!isObjectValueEqual(a[prop], b[prop])) + return false; + } else if (a[prop] !== b[prop]) { + return false; + } + } else { + return false; + } + } + return true; + } + Loader { id: wsocket - sourceComponent: WSocket { - Component.onCompleted: { - let ini_data = openFile("../bin/advConfig.ini"); - ini_data = ini_data.toLowerCase(); - let cfg = parseINIString(ini_data); - if(cfg["server"]["ip"].toLowerCase() === "local") { - wsIp = "localhost"; - } else { - wsIp = cfg["server"]["ip"]; - } - wsPort = cfg["server"]["port"]; - }} - active: widgetsNum>0 + sourceComponent: WSocket {} + active: false + } + + onWidgetsNumChanged: { + wsocket.active = widgetsNum>0; } function setWsocket(status) { wsocket.active = status; } - onWsocketClosed: { - if (wsocket.active) { + function rebootServer(force) { + if (force || !debug && wsocket.active && rebootFlag) { console.log("Try to reboot ADVServer..."); - execute("../bin/ADVServer.exe", "-reboot"); + execute(serverEXE, "-reboot"); wsocket.active = false; wsocket.active = true; + rebootFlag = false; } } + onDebugChanged: { + rebootServer(); + } + + onRebootFlagChanged: { + rebootServer(); + } + function parse_resource(resource_list, sort) { if (sort) resource_list.sort(function (x, y) { @@ -120,5 +207,14 @@ Item { Component.onCompleted: { updateStyleList(); + let ini_data = readFile(iniFile); + if (ini_data) { + ini_data = ini_data.toLowerCase(); + let cfg = parseINIString(ini_data); + serverCFG = Object.assign(defaultServerCFG, cfg); + } else { + let ini_text = convertINIString(defaultServerCFG); + writeFile(iniFile, ini_text); + } } } diff --git a/qml/Main.qml b/qml/Main.qml index 50e4436..e347a57 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -55,13 +55,19 @@ T.Widget { menu: Menu { Action { - text: qsTr("Settings") + "..." - enabled: !styleDialog.active + text: qsTr("Style Settings") + "..." onTriggered: { Common.updateStyleList(); styleDialog.active = true; } } + + Action { + text: qsTr("Server Settings") + "..." + onTriggered: { + Common.serverPreferencesOpen(); + } + } } Component.onCompleted: { diff --git a/qml/ServerPreferences.qml b/qml/ServerPreferences.qml new file mode 100644 index 0000000..2a20a3f --- /dev/null +++ b/qml/ServerPreferences.qml @@ -0,0 +1,220 @@ +import QtQuick 2.12 + +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import NERvGear 1.0 as NVG +import NERvGear.Preferences 1.0 as P +import NERvGear.Controls 1.0 + +import "." + + +NVG.Window { + title: qsTr("ADV-Server: Settings") + visible: true + minimumWidth: 480 + minimumHeight: 600 + width: minimumWidth + height: minimumHeight + + Page { + id: cfg_page + anchors.fill: parent + + header: TitleBar { + text: qsTr("Server Settings") + standardButtons: Dialog.Save | Dialog.Reset + + onAccepted: { + let cfg = Object.assign(Common.deepClone(Common.serverCFG), rootPreference.save()); + + if (cfg.server.logger) { + Common.debug = _debug.value; + } else { + Common.debug = false; + } + + if (!Common.isObjectValueEqual(cfg, Common.serverCFG)) { + Common.setWsocket(false); + Common.serverCFG = cfg; + + let ini_text = Common.convertINIString(Common.serverCFG); + Common.writeFile(Common.iniFile, ini_text); + Common.rebootServer(true); + Common.setWsocket(true); + } + + serverDialog.active = false; + } + + onReset: { + rootPreference.load(Common.defaultServerCFG); + } + } + + ColumnLayout { + id: root + anchors.fill: parent + anchors.margins: 16 + anchors.topMargin: 0 + + Flickable { + Layout.fillWidth: true + Layout.fillHeight: true + + clip: true + contentWidth: preferenceLayout.implicitWidth + contentHeight: preferenceLayout.implicitHeight + + ColumnLayout { + id: preferenceLayout + width: root.width + + P.SwitchPreference { + id: _debug + Layout.fillWidth: true + name: "debug" + label: qsTr("Debug Mode") + message: qsTr("Logging must be enabled and saved.") + warning: value ? qsTr("This will disable the error recovery function!") : "" + enabled: Common.serverCFG.server.logger + defaultValue: Common.debug + onPreferenceEdited: { + Common.debug = value; + } + } + + P.PreferenceGroup { + id: rootPreference + Layout.fillWidth: true + + P.PreferenceGroup { + Layout.fillWidth: true + name: "server" + Heading { text: qsTr("General") } + + P.SpinPreference { + name: "port" + label: qsTr("Port") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: 65535 + value: String(value) + defaultValue: Common.defaultServerCFG["server"]["port"] + } + + P.SpinPreference { + name: "maxclient" + label: qsTr("Max Number of Clients") + message: qsTr("Maximum number of server connections\n(All ADV widgets share one connection).") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: 15 + value: String(value) + defaultValue: Common.defaultServerCFG["server"]["maxclient"] + } + + P.SwitchPreference { + id: _server_logger + name: "logger" + label: qsTr("Enable Logging") + message: qsTr("Enable to output log file (ADV_Log.log).") + defaultValue: Common.defaultServerCFG["server"]["logger"] + } + + P.ItemPreference { + label: qsTr("Open Log File") + Layout.fillWidth: true + enabled: _debug.enabled + select: function () { + NVG.SystemCall.execute("explorer", NVG.Url.toLocalFile(Qt.resolvedUrl("../bin/ADV_Log.log")).replace(/\//g, '\\')); + } + } + } + + P.PreferenceGroup { + Layout.fillWidth: true + name: "fft" + Heading { text: qsTr("Data") } + + P.SpinPreference { + name: "attack" + label: qsTr("Increase Speed") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: 200 + defaultValue: Common.defaultServerCFG["fft"]["attack"] + } + + P.SpinPreference { + name: "decay" + label: qsTr("Reduction Speed") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: 200 + defaultValue: Common.defaultServerCFG["fft"]["decay"] + } + + P.SpinPreference { + name: "peakthr" + label: qsTr("Peak Extra Increment") + message: qsTr("Extra increment of data normalization peak.") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 0 + to: 1000 + defaultValue: Common.defaultServerCFG["fft"]["peakthr"] + } + + P.SpinPreference { + name: "norspeed" + label: qsTr("Dynamic Normalization Factor") + message: qsTr("Convergence speed of data normalization peak.") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: 100 + defaultValue: Common.defaultServerCFG["fft"]["norspeed"] + } + + P.SpinPreference { + id: _fps + name: "fps" + label: qsTr("Transmission Rate") + message: qsTr("Number of data sent per second.") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 10 + to: 60 + defaultValue: Common.defaultServerCFG["fft"]["fps"] + } + + P.SpinPreference { + name: "changespeed" + label: qsTr("Change Speed") + message: qsTr("Adjust the data change speed.") + display: P.TextFieldPreference.ExpandLabel + editable: true + from: 1 + to: _fps.value - 1 + defaultValue: Common.defaultServerCFG["fft"]["changespeed"] + } + } + + Component.onCompleted: { + rootPreference.load(Common.serverCFG) + } + } + } + } + } + } + + onClosing: { + serverDialog.active = false; + } +} diff --git a/qml/StylePreferences.qml b/qml/StylePreferences.qml index fa3e885..a047a13 100644 --- a/qml/StylePreferences.qml +++ b/qml/StylePreferences.qml @@ -11,7 +11,7 @@ import "." NVG.Window { id: window - title: qsTr("ADV-Plugin: Settings") + title: qsTr("ADV-Style: Settings") visible: true minimumWidth: 480 minimumHeight: 600 @@ -26,7 +26,7 @@ NVG.Window { anchors.fill: parent header: TitleBar { - text: qsTr("Settings") + text: qsTr("Style Settings") standardButtons: Dialog.Save | Dialog.Reset onAccepted: { @@ -82,7 +82,7 @@ NVG.Window { defaultValue: 0 model: Common.styles } - + Heading { id: heading text: Common.styles[styleList.value] + " " + qsTr("Configuration") diff --git a/qml/WSocket.qml b/qml/WSocket.qml index 03c410f..4ac18c2 100644 --- a/qml/WSocket.qml +++ b/qml/WSocket.qml @@ -4,15 +4,12 @@ import "." WebSocket { - property string wsIp: "localhost" - property int wsPort: 5050 - - url: "ws://" + wsIp + ":" + wsPort + url: "ws://" + Common.wsIp + ":" + Common.wsPort active: true onStatusChanged: { if(status === WebSocket.Closed || status === WebSocket.Error) { - Common.wsocketClosed(); + Common.rebootFlag = true; } }