import QtQuick import QtQuick.Layouts import QtQuick.Controls import QtQuick.Dialogs import ISC // ===== Split Dialog (Threshold Segmentation) ===== // Modal popup for segmenting temperature profile into Ramp/Soak/Cool phases. // Backend: splitter.segment_profile() returns list of DataSegment objects. Popup { id: root modal: true dim: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside width: Math.min(parent.width - 100, 800) height: Math.min(parent.height - 100, 600) anchors.centerIn: Overlay.overlay property real threshold: 40.0 property bool segmenting: false property var segments: [] property string currentFile: streamController.loadedFile || "" background: Rectangle { radius: Theme.radiusMd color: Theme.cardBackground border.color: Theme.cardBorder border.width: 1 } ColumnLayout { anchors.fill: parent anchors.margins: 20 spacing: 16 // ── Header ──────────────────────────────────────────────── RowLayout { Layout.fillWidth: true spacing: 10 Label { text: "SPLIT DATA" font.pixelSize: Theme.fontLg font.bold: true color: Theme.headingColor Layout.fillWidth: true } Button { text: "\u2715" flat: true Layout.preferredWidth: 32 Layout.preferredHeight: 32 onClicked: root.close() background: Rectangle { radius: Theme.radiusXs color: parent.hovered ? Theme.buttonNeutralHover : "transparent" } contentItem: Label { text: parent.text color: Theme.bodyColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } // ── Threshold Input ─────────────────────────────────────── RowLayout { Layout.fillWidth: true spacing: 16 ColumnLayout { Layout.fillWidth: true spacing: 4 Label { text: "THRESHOLD TEMPERATURE (°C)" color: Theme.sideMutedText font.pixelSize: Theme.fontSm font.bold: true font.letterSpacing: 1.0 } RowLayout { Layout.fillWidth: true spacing: 8 SpinBox { id: thresholdSpin from: 0 to: 200 value: root.threshold stepSize: 1 editable: true Layout.fillWidth: true background: Rectangle { color: Theme.fieldBackground border.color: Theme.cardBorder border.width: 1 radius: Theme.radiusSm } } Label { text: "°C" color: Theme.bodyColor font.pixelSize: Theme.fontMd verticalAlignment: Text.AlignVCenter } } } ColumnLayout { Layout.fillWidth: true spacing: 4 Label { text: "SESSION CSV" color: Theme.sideMutedText font.pixelSize: Theme.fontSm font.bold: true font.letterSpacing: 1.0 } Label { Layout.fillWidth: true text: root.currentFile !== "" ? root.currentFile.split("/").pop() : "No file loaded" color: root.currentFile !== "" ? Theme.bodyColor : Theme.sideMutedText font.pixelSize: Theme.fontSm elide: Text.ElideMiddle } } } // ── Split Button ────────────────────────────────────────── RowLayout { Layout.fillWidth: true spacing: 10 Button { id: splitBtn text: "SPLIT" Layout.fillWidth: true enabled: !root.segmenting && root.currentFile !== "" onClicked: { root.threshold = thresholdSpin.value; root.segmenting = true; streamController.splitData(root.currentFile, root.threshold); } background: Rectangle { radius: Theme.radiusSm color: splitBtn.enabled ? Theme.primaryAccent : Theme.buttonNeutralBackground border.color: splitBtn.enabled ? Theme.primaryAccent : "transparent" border.width: 1 } contentItem: Row { spacing: 8 anchors.centerIn: parent Label { text: splitBtn.enabled ? (root.segmenting ? "Segmenting..." : "SPLIT") : "LOAD DATA FIRST" color: splitBtn.enabled ? Theme.tone100 : Theme.sideMutedText font.pixelSize: Theme.fontSm font.bold: true font.letterSpacing: 1.0 } BusyIndicator { running: root.segmenting visible: root.segmenting width: 16 height: 16 } } } } // ── Segments Table ──────────────────────────────────────── Rectangle { Layout.fillWidth: true Layout.fillHeight: true radius: Theme.radiusSm color: Theme.panelBackground border.color: Theme.cardBorder border.width: 1 ColumnLayout { anchors.fill: parent anchors.margins: 0 spacing: 0 // Table header Rectangle { Layout.fillWidth: true Layout.preferredHeight: 36 color: Theme.subtleSectionBackground border.color: Theme.cardBorder border.width: 1 RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 8 Label { text: "PHASE"; Layout.preferredWidth: 100; color: Theme.sideMutedText; font.bold: true; font.pixelSize: Theme.fontSm } Label { text: "START (s)"; Layout.preferredWidth: 120; color: Theme.sideMutedText; font.bold: true; font.pixelSize: Theme.fontSm } Label { text: "END (s)"; Layout.preferredWidth: 120; color: Theme.sideMutedText; font.bold: true; font.pixelSize: Theme.fontSm } Label { text: "AVG TEMP (°C)"; Layout.fillWidth: true; color: Theme.sideMutedText; font.bold: true; font.pixelSize: Theme.fontSm } Item { Layout.preferredWidth: 80 } } } // Table body (scrollable) ScrollView { Layout.fillWidth: true Layout.fillHeight: true clip: true ColumnLayout { width: parent.width - 32 spacing: 0 Repeater { model: root.segments.length > 0 ? root.segments : [] delegate: Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 color: index % 2 === 0 ? Theme.cardBackground : "transparent" border.color: Theme.cardBorder RowLayout { anchors.fill: parent anchors.margins: 12 spacing: 8 Label { text: modelData.label || "" Layout.preferredWidth: 100 color: modelData.label === "Soak" ? Theme.statusSuccessColor : modelData.label === "Ramp" ? Theme.statusWarningColor : Theme.bodyColor font.bold: true font.pixelSize: Theme.fontSm } Label { text: (modelData.start_frame || 0).toString() Layout.preferredWidth: 120 color: Theme.bodyColor font.pixelSize: Theme.fontSm } Label { text: (modelData.end_frame || 0).toString() Layout.preferredWidth: 120 color: Theme.bodyColor font.pixelSize: Theme.fontSm } Label { text: (modelData.avg_temp || 0).toFixed(2) + " °C" Layout.fillWidth: true color: Theme.bodyColor font.pixelSize: Theme.fontSm } Button { id: exportBtn text: "Export" Layout.preferredWidth: 80 Layout.preferredHeight: 26 hoverEnabled: true onClicked: streamController.exportSegment(index) background: Rectangle { radius: Theme.radiusXs color: exportBtn.pressed ? Theme.buttonNeutralPressed : exportBtn.hovered ? Theme.buttonNeutralHover : Theme.buttonNeutralBackground border.color: Theme.fieldBorder border.width: 1 } contentItem: Label { text: exportBtn.text color: Theme.buttonNeutralText font.pixelSize: Theme.fontXs horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } } // Empty state Label { visible: root.segments.length === 0 text: root.currentFile !== "" ? "Click SPLIT to segment data" : "Load a CSV file first" color: Theme.sideMutedText font.pixelSize: Theme.fontSm Layout.fillWidth: true Layout.preferredHeight: 100 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } } } } } // ── Summary line ────────────────────────────────────────── Label { visible: root.segments.length > 0 text: root.segments.length + " segment" + (root.segments.length === 1 ? "" : "s") + " found above " + root.threshold.toFixed(0) + "°C threshold." color: Theme.bodyColor font.pixelSize: Theme.fontSm } // ── Export feedback ─────────────────────────────────────── Label { id: exportStatus visible: text !== "" color: Theme.bodyColor font.pixelSize: Theme.fontSm elide: Text.ElideMiddle Layout.fillWidth: true } } // ── Backend Connection ──────────────────────────────────────── Connections { target: streamController function onSplitResult(result) { root.segmenting = false; exportStatus.text = ""; if (result && result.success) { root.segments = result.segments || []; console.log("[SplitDialog] Segmented into", root.segments.length, "phases"); } else { root.segments = []; console.log("[SplitDialog] Split failed:", result.error || "unknown"); } } function onSegmentExported(result) { if (result && result.success) exportStatus.text = "Exported: " + result.path.split("/").pop(); else exportStatus.text = "Export failed: " + (result.error || "unknown"); } } }