Skip to content
Closed
12 changes: 12 additions & 0 deletions .github/workflows/arstack-studio-native-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ jobs:
}
$checks | Set-Content (Join-Path $PWD 'dist\SHA256SUMS.txt') -Encoding ascii

# Publish a small single-installer artifact BEFORE long Windows upgrade
# acceptance. Operators can bench the exact source SHA without waiting
# for the full portable+firmware ZIP, while CI still validates upgrades.
- name: Upload standalone Windows setup EXE early
uses: actions/upload-artifact@v4
with:
name: arstack-studio-setup-exe-${{ github.event.pull_request.number || github.run_number }}-${{ github.sha }}
path: dist/ARStack-Studio-Preview-win-x64-setup.exe
if-no-files-found: error
retention-days: 7
compression-level: 0

- name: Inno clean install, running-app upgrade and clean uninstall
shell: pwsh
run: |
Expand Down
82 changes: 60 additions & 22 deletions apps/arstack_studio/qml/Main.qml
Original file line number Diff line number Diff line change
Expand Up @@ -431,16 +431,20 @@ ApplicationWindow {
}

function parseOperatorNumber(text) {
// Dot and comma are alternative DECIMAL separators, irrespective of
// Windows/keyboard locale. Never guess thousands separators or
// silently reinterpret ambiguous mixed punctuation.
var normalized = String(text).trim()
.replace(/\s/g, "")
.replace(/[\s\u00a0]/g, "")
.replace(/degrees?/ig, "")
.replace(/deg/ig, "")
.replace(/°/g, "")
.replace(/Hz/ig, "")
.replace(/[AV]$/i, "")
if (normalized.indexOf(",") >= 0 && normalized.indexOf(".") < 0)
normalized = normalized.replace(",", ".")
if (!normalized.length || normalized === "+" || normalized === "-" || normalized === ".")
if (normalized.indexOf(",") !== -1 && normalized.indexOf(".") !== -1)
return NaN
normalized = normalized.replace(/,/g, ".")
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/.test(normalized))
Comment on lines +444 to +447

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove the locale-sensitive validator before accepting both separators

For the frequency editor, the new normalization does not actually make both 0.5 and 0,5 enterable regardless of locale: frequencyField still has a locale-aware DoubleValidator at line 1176, which validates or rejects keystrokes before parseOperatorNumber() receives them. Thus on locales whose decimal separator differs from the typed character, the alternate form can be filtered or interpreted under grouping rules; the field needs a validator that explicitly accepts both syntaxes, or validation must be left to this parser.

Useful? React with 👍 / 👎.

return NaN
return Number(normalized)
}
Expand Down Expand Up @@ -1005,10 +1009,29 @@ ApplicationWindow {
}

ColumnLayout {
id: mainWorkspaceLayout
anchors.fill: parent
anchors.margins: 8
spacing: 8

// Passive parent-level tap: clicking an unused surface also commits
// an edited channel field. A tap on that same field keeps editing.
// Child controls retain ownership of their own clicks/key handling.
TapHandler {
acceptedButtons: Qt.LeftButton
gesturePolicy: TapHandler.DragThreshold
onTapped: function(point) {
const editor = root.activeFocusItem
if (!editor || (editor.objectName !== "matrixMagnitudeEditor" &&
editor.objectName !== "matrixPhaseEditor")) return
const local = editor.mapFromItem(mainWorkspaceLayout,
point.position.x, point.position.y)
if (local.x < 0 || local.y < 0 ||
local.x >= editor.width || local.y >= editor.height)
editor.focus = false
}
}

WorkflowBar {
id: workflowBar
objectName: "studioWorkflowBar"
Expand Down Expand Up @@ -1062,12 +1085,17 @@ ApplicationWindow {
}

SurfacePanel {
id: injectionPanel
objectName: "studioInjectionSurface"
SplitView.fillWidth: true
SplitView.fillHeight: true
SplitView.minimumWidth: root.compactLayout ? 500 : 540
SplitView.preferredWidth: root.compactLayout ? 600 : 690
SplitView.maximumWidth: 750
// Proportional initial balance. The edit table is a
// narrow control surface, not a full-width spreadsheet.
// Splitter remains operator-draggable within safe limits.
SplitView.minimumWidth: 405
SplitView.preferredWidth: Math.max(420,
Math.min(485, Math.round(workspaceSplit.width * 0.41)))
SplitView.maximumWidth: 535
theme: studioTheme

ColumnLayout {
Expand Down Expand Up @@ -1118,10 +1146,10 @@ ApplicationWindow {
anchors.fill: parent
anchors.leftMargin: 11
anchors.rightMargin: 11
spacing: 8
spacing: root.compactLayout ? 4 : 8

Label {
text: "Frequency"
text: root.compactLayout ? "Freq" : "Frequency"
color: studioTheme.textSoft
font.family: root.uiFont
font.pixelSize: 10
Expand All @@ -1141,7 +1169,8 @@ ApplicationWindow {
theme: studioTheme
monoFont: root.monoFont
compact: root.compactLayout
implicitWidth: 90
implicitWidth: root.compactLayout ? 76 : 90
Layout.preferredWidth: root.compactLayout ? 76 : 90
text: "50.000"
suffixText: "Hz"
enabled: true
Expand All @@ -1168,14 +1197,15 @@ ApplicationWindow {
}
}

CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "0"; implicitWidth: 44; toolTipText: "DC"; onClicked: root.setFrequencyValue(0) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "50"; implicitWidth: 44; onClicked: root.setFrequencyValue(50) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "60"; implicitWidth: 44; onClicked: root.setFrequencyValue(60) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "0"; implicitWidth: root.compactLayout ? 32 : 44; toolTipText: "DC"; onClicked: root.setFrequencyValue(0) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "50"; implicitWidth: root.compactLayout ? 32 : 44; onClicked: root.setFrequencyValue(50) }
CalmButton { theme: studioTheme; uiFont: root.uiFont; text: "60"; implicitWidth: root.compactLayout ? 32 : 44; onClicked: root.setFrequencyValue(60) }
Rectangle { width: 1; height: 24; color: studioTheme.lineSoft }
StudioLinkSwitch {
theme: studioTheme
uiFont: root.uiFont
enabled: root.signalFrequency > 0 && !workflowBar.startQueued
text: root.compactLayout ? "Link A/B/C" : "3-phase link"
checked: root.phaseLink
onToggled: {
// Link is an editing preference, not a wire
Expand Down Expand Up @@ -1268,14 +1298,22 @@ ApplicationWindow {
Rectangle { width: 6; height: 6; radius: 3; color: studioTheme.accent }
Label { text: root.activeSignal; color: studioTheme.textSoft; font.family: root.monoFont; font.pixelSize: studioTheme.labelSize; font.weight: Font.Bold }
Label {
// Redundant selection readback yields space
// to Quality controls in the narrow table.
visible: injectionPanel.width >= 505
text: root.signalFrequency === 0
? root.activeMagnitude.toFixed(3) + " " + root.activeUnit + " DC"
: root.activeMagnitude.toFixed(3) + " " + root.activeUnit + " RMS · " + root.activePhase.toFixed(2) + "°"
color: studioTheme.muted
font.family: root.uiFont
font.pixelSize: 9
}
Rectangle { width: 1; height: 20; color: studioTheme.lineSoft }
Rectangle {
visible: injectionPanel.width >= 505
width: 1
height: 20
color: studioTheme.lineSoft
}
QualityEditor {
enabled: !workflowBar.startQueued
Layout.fillWidth: true
Expand All @@ -1296,8 +1334,9 @@ ApplicationWindow {
SplitView {
visible: (root.phasorDockVisible && !root.phasorDetached) ||
(root.waveformDockVisible && !root.waveformDetached)
SplitView.preferredWidth: root.compactLayout ? 450 : 470
SplitView.minimumWidth: root.compactLayout ? 300 : 340
SplitView.preferredWidth: Math.max(490,
Math.round(workspaceSplit.width * 0.59))
SplitView.minimumWidth: 360
SplitView.maximumWidth: 680
SplitView.fillHeight: true
orientation: Qt.Vertical
Expand All @@ -1317,10 +1356,9 @@ ApplicationWindow {
DockFrame {
visible: root.phasorDockVisible && !root.phasorDetached
SplitView.fillWidth: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 225
SplitView.maximumHeight: 265
SplitView.fillHeight: false
SplitView.minimumHeight: 210
SplitView.preferredHeight: 310
SplitView.fillHeight: true
theme: studioTheme
titleText: "Phasor"
statusText: ""
Expand Down Expand Up @@ -1357,8 +1395,8 @@ ApplicationWindow {
DockFrame {
visible: root.waveformDockVisible && !root.waveformDetached
SplitView.fillWidth: true
SplitView.minimumHeight: 170
SplitView.preferredHeight: 328
SplitView.minimumHeight: 210
SplitView.preferredHeight: 310
SplitView.fillHeight: true
theme: studioTheme
titleText: "Waveform"
Expand Down
Loading
Loading