mirror of
https://github.com/wf-group/wfview
synced 2026-08-13 17:32:59 -04:00
1963 lines
80 KiB
QML
1963 lines
80 KiB
QML
// Main.qml
|
|
import QtQuick 2.15
|
|
import QtQuick.Window 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
import WFVIEW 1.0
|
|
|
|
ApplicationWindow {
|
|
id: win
|
|
color: palette.window
|
|
|
|
title: radioConnected ? MainController.windowTitle : qsTr("wfview")
|
|
|
|
property int connStatus: Number(MainController.connStatus)
|
|
property var txAudioProcessingWindow: null
|
|
property var rxAudioProcessingWindow: null
|
|
property var rigCreatorWindows: []
|
|
property bool quitConfirmed: false
|
|
property bool shutdownStarted: false
|
|
property var savedStartupGeometry: null
|
|
property var lastConnectedGeometry: null
|
|
property bool startupGeometryPending: false
|
|
property bool connectedGeometryRestored: false
|
|
property int startupGeometryRestoreAttempts: 0
|
|
property bool wasRadioConnected: false
|
|
readonly property int contentHorizontalPadding: 8
|
|
readonly property int contentTopPadding: 4
|
|
readonly property int mainControlSpacing: 6
|
|
readonly property int mainControlSliderHeight: 120
|
|
readonly property int mainControlDialSize: mainControlSliderHeight
|
|
readonly property int ritDialSize: 44
|
|
readonly property int optionalMeterWidth: 300
|
|
readonly property int optionalMeterHeight: 40
|
|
readonly property bool radioConnected: Number(MainController.connStatus) === 2
|
|
readonly property bool waylandPlatform: String(MainController.platformName()).indexOf("wayland") !== -1
|
|
readonly property var mainControlSpecs: MainController.uiSpecs["mainControls"] || ({})
|
|
readonly property var firstReceiver: MainController.receiverCount > 0 ? MainController.receiver(0) : null
|
|
readonly property bool _sysTheme: MainController.settings.options["Interface.UseSystemTheme"] === true
|
|
|
|
property int flowSpacing: mainControlSpacing
|
|
property bool cmdTwoColumns: false
|
|
property int scopeFlowPaneWidth: 320
|
|
property bool _flowLayoutInitialized: false
|
|
|
|
Timer {
|
|
id: flowLayoutTimer
|
|
interval: 150
|
|
onTriggered: win.applyFlowThresholds()
|
|
}
|
|
|
|
Timer {
|
|
id: heightReleaseTimer
|
|
interval: 300
|
|
onTriggered: {
|
|
mainGroup.targetHeight = Qt.binding(function() {
|
|
return Math.max(leftControlsColumn.implicitHeight, mainControlsFlow.implicitHeight)
|
|
})
|
|
}
|
|
}
|
|
|
|
function applyFlowThresholds() {
|
|
var flowW = mainControlsFlow.width
|
|
var changed = false
|
|
|
|
if (!cmdTwoColumns && flowW >= 930) {
|
|
cmdTwoColumns = true
|
|
changed = true
|
|
} else if (cmdTwoColumns && flowW < 870) {
|
|
cmdTwoColumns = false
|
|
changed = true
|
|
}
|
|
|
|
if (scopeFlowPaneWidth === 320 && flowW >= 1680) {
|
|
scopeFlowPaneWidth = 640
|
|
changed = true
|
|
} else if (scopeFlowPaneWidth === 640 && flowW < 1620) {
|
|
scopeFlowPaneWidth = 320
|
|
changed = true
|
|
}
|
|
|
|
if (changed) {
|
|
mainGroup.targetHeight = mainGroup.height
|
|
heightReleaseTimer.restart()
|
|
}
|
|
}
|
|
|
|
function updateFlowLayout() {
|
|
var w = win.width
|
|
flowSpacing = w >= 1500 ? 12 : (w >= 1200 ? 8 : mainControlSpacing)
|
|
|
|
if (!_flowLayoutInitialized) {
|
|
_flowLayoutInitialized = true
|
|
applyFlowThresholds()
|
|
} else {
|
|
flowLayoutTimer.restart()
|
|
}
|
|
}
|
|
|
|
width: 946
|
|
height: minimumHeight
|
|
visible: false
|
|
|
|
minimumWidth: radioConnected ? 360 : mainLayout.implicitWidth + contentHorizontalPadding * 2
|
|
//minimumHeight: mainLayout.implicitHeight+30
|
|
minimumHeight: mainLayout.implicitHeight + 8
|
|
+ mainGroup.padding * 2
|
|
+ scopeVFOGroup.padding * 2
|
|
+ mainLayout.spacing * 2
|
|
onMinimumWidthChanged: {
|
|
if (!win.radioConnected && !win.waylandPlatform)
|
|
win.width = win.minimumWidth
|
|
}
|
|
onMinimumHeightChanged: {
|
|
if (!win.radioConnected && !win.waylandPlatform)
|
|
win.height = win.minimumHeight
|
|
}
|
|
|
|
component OptionalMeterSlot: Item {
|
|
id: optionalMeterSlot
|
|
|
|
property int slot: 2
|
|
readonly property string optionKey: slot === 2 ? "Interface.Meter2Type" : "Interface.Meter3Type"
|
|
readonly property int meterChoice: Number(MainController.settings.options[optionKey] || 0)
|
|
readonly property double meterLevel: slot === 2 ? MainController.optionalMeter2Level : MainController.optionalMeter3Level
|
|
|
|
width: 300
|
|
height: 40
|
|
|
|
function applyExtremities() {
|
|
const ext = MainController.optionalMeterExtremities(meterChoice)
|
|
if (ext.valid) {
|
|
optionalMeter.setMeterExtremities(ext.low, ext.high, ext.red)
|
|
optionalMeter.warningText = ""
|
|
} else {
|
|
optionalMeter.clearMeterExtremities()
|
|
optionalMeter.warningText = ext.calibrationMissing ? qsTr("No calibration found") : ""
|
|
}
|
|
}
|
|
|
|
Meter {
|
|
id: optionalMeter
|
|
anchors.fill: parent
|
|
visible: optionalMeterSlot.meterChoice !== 0
|
|
meterType: optionalMeterSlot.meterChoice
|
|
current: optionalMeterSlot.meterLevel
|
|
drawLabels: true
|
|
reverseCompMeter: win.settingAsBool(MainController.settings.options["Interface.CompMeterReverse"], false)
|
|
scaleTextColor: win.palette.windowText
|
|
scaleLineColor: win.palette.windowText
|
|
scaleHighTextColor: MainController.settings.options["Color.MeterHighScale"]
|
|
scaleHighLineColor: MainController.settings.options["Color.MeterHighScale"]
|
|
onConfigureMeterRequested: meterMenu.open()
|
|
Component.onCompleted: optionalMeterSlot.applyExtremities()
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
visible: optionalMeterSlot.meterChoice === 0
|
|
color: Qt.rgba(win.palette.button.r, win.palette.button.g, win.palette.button.b, 0.35)
|
|
border.color: win.palette.mid
|
|
|
|
Label {
|
|
anchors.centerIn: parent
|
|
text: qsTr("Double-click to add meter")
|
|
color: win.palette.windowText
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.LeftButton
|
|
onDoubleClicked: meterMenu.open()
|
|
}
|
|
|
|
Popup {
|
|
id: meterMenu
|
|
x: 0
|
|
y: -implicitHeight - win.mainControlSpacing
|
|
modal: false
|
|
focus: true
|
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
|
padding: 4
|
|
|
|
ColumnLayout {
|
|
spacing: 2
|
|
Repeater {
|
|
model: MainController.optionalMeterOptions()
|
|
Button {
|
|
Layout.fillWidth: true
|
|
text: modelData.text === "" ? qsTr("None") : modelData.text
|
|
enabled: modelData.available
|
|
checkable: true
|
|
checked: optionalMeterSlot.meterChoice === modelData.value
|
|
onClicked: {
|
|
MainController.setOptionalMeterType(optionalMeterSlot.slot, modelData.value)
|
|
meterMenu.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onMeterChoiceChanged: applyExtremities()
|
|
Connections {
|
|
target: MainController
|
|
function onUiSpecsChanged() { optionalMeterSlot.applyExtremities() }
|
|
}
|
|
}
|
|
|
|
component AudioLevelBar: Item {
|
|
id: audioLevelBar
|
|
|
|
property int value: 0
|
|
property string label: ""
|
|
property bool active: true
|
|
readonly property real normalizedValue: Math.max(0, Math.min(255, value)) / 255
|
|
|
|
Layout.preferredWidth: 14
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
opacity: active ? 1.0 : 0.35
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: win.palette.base
|
|
border.color: win.palette.mid
|
|
radius: 2
|
|
|
|
Rectangle {
|
|
id: levelClip
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
anchors.margins: 2
|
|
height: Math.max(0, parent.height - 4) * audioLevelBar.normalizedValue
|
|
radius: 1
|
|
color: "transparent"
|
|
clip: true
|
|
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
height: Math.max(0, audioLevelBar.height - 4)
|
|
radius: 1
|
|
gradient: Gradient {
|
|
orientation: Gradient.Vertical
|
|
GradientStop { position: 0.0; color: "#d32f2f" }
|
|
GradientStop { position: 0.05; color: "#d32f2f" }
|
|
GradientStop { position: 0.20; color: "#fbc02d" }
|
|
GradientStop { position: 0.80; color: "#2e7d32" }
|
|
GradientStop { position: 1.0; color: "#2e7d32" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HoverHandler { id: audioLevelHover }
|
|
ToolTip.visible: audioLevelHover.hovered
|
|
ToolTip.text: audioLevelBar.label + ": " + audioLevelBar.value
|
|
ToolTip.delay: 300
|
|
}
|
|
|
|
LoggingWindow {
|
|
id: loggingWindow
|
|
}
|
|
|
|
function scheduleStartupGeometryRestore() {
|
|
if (win.waylandPlatform) {
|
|
win.startupGeometryPending = false
|
|
return
|
|
}
|
|
startupGeometryTimer.restart()
|
|
}
|
|
|
|
Timer {
|
|
id: startupGeometryTimer
|
|
interval: 150
|
|
repeat: false
|
|
onTriggered: {
|
|
if (win.waylandPlatform) {
|
|
win.startupGeometryPending = false
|
|
return
|
|
}
|
|
|
|
if (!win.startupGeometryPending || !win.radioConnected
|
|
|| !win.savedStartupGeometry || !win.savedStartupGeometry.valid)
|
|
return
|
|
|
|
if (MainController.receiverCount <= 0 && win.startupGeometryRestoreAttempts < 20) {
|
|
win.startupGeometryRestoreAttempts += 1
|
|
win.scheduleStartupGeometryRestore()
|
|
return
|
|
}
|
|
|
|
win.applyWindowGeometry(win.savedStartupGeometry)
|
|
|
|
if (win.startupGeometryRestoreAttempts < 8) {
|
|
win.startupGeometryRestoreAttempts += 1
|
|
win.scheduleStartupGeometryRestore()
|
|
} else {
|
|
win.startupGeometryPending = false
|
|
win.connectedGeometryRestored = true
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: MainController
|
|
function onReceiverCountChanged() {
|
|
if (win.startupGeometryPending)
|
|
win.scheduleStartupGeometryRestore()
|
|
}
|
|
}
|
|
|
|
onRadioConnectedChanged: {
|
|
if (win.waylandPlatform) {
|
|
win.wasRadioConnected = win.radioConnected
|
|
return
|
|
}
|
|
if (win.radioConnected) {
|
|
win.restoreConnectedWindowGeometry()
|
|
} else {
|
|
if (win.wasRadioConnected)
|
|
win.rememberConnectedWindowGeometry()
|
|
win.connectedGeometryRestored = false
|
|
win.startupGeometryPending = Boolean(win.savedStartupGeometry && win.savedStartupGeometry.valid)
|
|
win.startupGeometryRestoreAttempts = 0
|
|
Qt.callLater(win.shrinkDisconnectedWindow)
|
|
}
|
|
win.wasRadioConnected = win.radioConnected
|
|
}
|
|
|
|
Dialog {
|
|
id: unsavedSettingsDialog
|
|
title: qsTr("Unsaved settings")
|
|
width: 420
|
|
height: 150
|
|
x: Math.round((win.width - width) / 2)
|
|
y: Math.round((win.height - height) / 2)
|
|
visible: false
|
|
modal: true
|
|
closePolicy: Popup.NoAutoClose
|
|
|
|
background: Rectangle {
|
|
color: win.palette.window
|
|
border.color: win.palette.highlight
|
|
border.width: 1
|
|
radius: 4
|
|
}
|
|
|
|
function showDialog() {
|
|
win.ensureWindowFitsPopup(unsavedSettingsDialog)
|
|
open()
|
|
forceActiveFocus()
|
|
}
|
|
|
|
onClosed: win.shrinkAfterDialog()
|
|
onRejected: close()
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: 16
|
|
spacing: 16
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Settings have changed since the last save.")
|
|
color: win.palette.windowText
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.alignment: Qt.AlignRight
|
|
spacing: 8
|
|
|
|
Button {
|
|
text: qsTr("Save")
|
|
onClicked: {
|
|
unsavedSettingsDialog.visible = false
|
|
win.finishQuit(true)
|
|
}
|
|
}
|
|
Button {
|
|
text: qsTr("Discard")
|
|
onClicked: {
|
|
unsavedSettingsDialog.visible = false
|
|
win.finishQuit(false)
|
|
}
|
|
}
|
|
Button {
|
|
text: qsTr("Cancel")
|
|
onClicked: unsavedSettingsDialog.visible = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
onClosing: function(close) {
|
|
if (win.shutdownStarted) {
|
|
close.accepted = true
|
|
return
|
|
}
|
|
|
|
if (!win.quitConfirmed && shouldConfirmUnsavedSettings()) {
|
|
close.accepted = false
|
|
unsavedSettingsDialog.showDialog()
|
|
return
|
|
}
|
|
|
|
close.accepted = false
|
|
shutdownAndQuit()
|
|
}
|
|
|
|
FirstTimeSetup {
|
|
id: firstTimeSetup
|
|
visible: false
|
|
|
|
onExitProgram: {
|
|
Qt.quit()
|
|
}
|
|
|
|
onShowSettings: function(isNetwork) {
|
|
showSettingsWindow()
|
|
win.visible = true
|
|
}
|
|
|
|
onSkipSetup: {
|
|
win.visible = true
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: settingsLoader
|
|
active: false
|
|
sourceComponent: Component {
|
|
Settings {
|
|
controller: MainController.settings
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create memories window on demand
|
|
Loader {
|
|
id: memoriesLoader
|
|
active: false
|
|
sourceComponent: Component {
|
|
Memories {
|
|
memoriesModel: MainController.memoriesModel
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: repeaterLoader
|
|
active: false
|
|
sourceComponent: Component {
|
|
RepeaterSetup {
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: selectRadioLoader
|
|
active: false
|
|
source: "qrc:/qml/SelectRadio.qml"
|
|
asynchronous: false
|
|
|
|
onLoaded: {
|
|
// Connect the window to MainController's radio controller
|
|
item.selectRadioController = MainController.selectRadio
|
|
}
|
|
}
|
|
|
|
// Load CWSender window
|
|
Loader {
|
|
id: cwSenderLoader
|
|
active: false
|
|
source: "qrc:/qml/CWSender.qml"
|
|
asynchronous: false
|
|
onLoaded: {
|
|
item.cwSenderController = MainController.cwSender
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: debugLoader
|
|
active: false
|
|
source: "qrc:/qml/Debug.qml"
|
|
|
|
onLoaded: {
|
|
// show + activate
|
|
item.visible = true
|
|
item.raise()
|
|
item.requestActivate()
|
|
|
|
// when closed, destroy it (frees controller/timers/models too)
|
|
item.closing.connect(function() {
|
|
debugLoader.active = false
|
|
})
|
|
}
|
|
}
|
|
|
|
function showDebugWindow() {
|
|
if (!debugLoader.active) {
|
|
debugLoader.active = true
|
|
} else {
|
|
debugLoader.item.visible = true
|
|
debugLoader.item.raise()
|
|
debugLoader.item.requestActivate()
|
|
}
|
|
}
|
|
|
|
function showSettingsWindow() {
|
|
if (!settingsLoader.active)
|
|
settingsLoader.active = true
|
|
if (settingsLoader.item)
|
|
settingsLoader.item.showOnScreen(win)
|
|
}
|
|
|
|
function showSelectRadioWindow() {
|
|
if (!selectRadioLoader.active)
|
|
selectRadioLoader.active = true
|
|
MainController.showSelectRadio()
|
|
Qt.callLater(function() {
|
|
if (selectRadioLoader.item) {
|
|
selectRadioLoader.item.raise()
|
|
selectRadioLoader.item.requestActivate()
|
|
}
|
|
})
|
|
}
|
|
|
|
function closeTopWindow() {
|
|
if (settingsLoader.item && settingsLoader.item.visible && settingsLoader.item.active) {
|
|
settingsLoader.item.close()
|
|
return
|
|
}
|
|
if (memoriesLoader.item && memoriesLoader.item.visible && memoriesLoader.item.active) {
|
|
memoriesLoader.item.close()
|
|
return
|
|
}
|
|
if (repeaterLoader.item && repeaterLoader.item.visible && repeaterLoader.item.active) {
|
|
repeaterLoader.item.close()
|
|
return
|
|
}
|
|
if (selectRadioLoader.item && selectRadioLoader.item.visible && selectRadioLoader.item.active) {
|
|
MainController.selectRadio.visible = false
|
|
return
|
|
}
|
|
if (cwSenderLoader.item && cwSenderLoader.item.visible && cwSenderLoader.item.active) {
|
|
MainController.cwSender.visible = false
|
|
return
|
|
}
|
|
if (debugLoader.item && debugLoader.item.visible && debugLoader.item.active) {
|
|
debugLoader.item.close()
|
|
return
|
|
}
|
|
for (var i = 0; i < receiversRepeater.count; ++i) {
|
|
var row = receiversRepeater.itemAt(i)
|
|
if (row && MainController.isReceiverDetached(i) && row.detachedWindowActive) {
|
|
MainController.setReceiverDetached(i, false)
|
|
return
|
|
}
|
|
}
|
|
if (win.active)
|
|
win.close()
|
|
}
|
|
|
|
function openMemoriesWindow() {
|
|
if (!win.radioConnected || !MainController.canOpenMemories())
|
|
return
|
|
MainController.openMemories()
|
|
if (!memoriesLoader.active)
|
|
memoriesLoader.active = true
|
|
Qt.callLater(function() {
|
|
if (memoriesLoader.item) {
|
|
memoriesLoader.item.visible = true
|
|
memoriesLoader.item.raise()
|
|
memoriesLoader.item.requestActivate()
|
|
}
|
|
})
|
|
}
|
|
|
|
function openRepeaterWindow() {
|
|
if (!win.radioConnected || !(mainControlSpecs.canRepeater ?? false))
|
|
return
|
|
if (!repeaterLoader.active)
|
|
repeaterLoader.active = true
|
|
Qt.callLater(function() {
|
|
if (repeaterLoader.item) {
|
|
repeaterLoader.item.visible = true
|
|
repeaterLoader.item.raise()
|
|
repeaterLoader.item.requestActivate()
|
|
}
|
|
})
|
|
}
|
|
|
|
function openCwSenderWindow() {
|
|
if (!(mainControlSpecs.canSendCW ?? false))
|
|
return
|
|
if (!cwSenderLoader.active)
|
|
cwSenderLoader.active = true
|
|
MainController.showCWSender()
|
|
Qt.callLater(function() {
|
|
if (cwSenderLoader.item) {
|
|
cwSenderLoader.item.raise()
|
|
cwSenderLoader.item.requestActivate()
|
|
}
|
|
})
|
|
}
|
|
|
|
function popoutReceiver(receiver) {
|
|
if (!win.radioConnected)
|
|
return
|
|
var i = Number(receiver)
|
|
var row = i >= 0 && i < receiversRepeater.count ? receiversRepeater.itemAt(i) : null
|
|
if (row && row.receiverController && row.receiverController.active && !MainController.isReceiverDetached(i))
|
|
MainController.setReceiverDetached(i, true)
|
|
}
|
|
|
|
function popinReceiver(receiver) {
|
|
var i = Number(receiver)
|
|
if (i >= 0 && i < receiversRepeater.count && MainController.isReceiverDetached(i))
|
|
MainController.setReceiverDetached(i, false)
|
|
}
|
|
|
|
function runConfiguredAppShortcut(commandName, receiver) {
|
|
const command = String(commandName)
|
|
if (command === "app.debug") {
|
|
showDebugWindow()
|
|
return
|
|
}
|
|
if (command === "app.raiseMainWindow") {
|
|
win.show()
|
|
win.raise()
|
|
win.requestActivate()
|
|
return
|
|
}
|
|
if (command === "app.openSettings") {
|
|
showSettingsWindow()
|
|
return
|
|
}
|
|
if (command === "app.toggleFullscreen") {
|
|
win.visibility = win.visibility === Window.FullScreen ? Window.Windowed : Window.FullScreen
|
|
return
|
|
}
|
|
if (command === "app.closeWindow") {
|
|
closeTopWindow()
|
|
return
|
|
}
|
|
if (command === "app.openMemories") {
|
|
openMemoriesWindow()
|
|
return
|
|
}
|
|
if (command === "app.openCwSender") {
|
|
openCwSenderWindow()
|
|
return
|
|
}
|
|
if (command === "app.popoutReceiver") {
|
|
popoutReceiver(receiver)
|
|
return
|
|
}
|
|
if (command === "app.popinReceiver") {
|
|
popinReceiver(receiver)
|
|
return
|
|
}
|
|
if (command.indexOf("app.") === 0) {
|
|
MainController.runShortcutAppAction(command)
|
|
return
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: MainController
|
|
function onAppShortcutActivated(commandName, receiver) {
|
|
win.runConfiguredAppShortcut(commandName, receiver)
|
|
}
|
|
}
|
|
|
|
// QWidget had acceptDrops=true
|
|
// In QML, you typically use DropArea
|
|
DropArea {
|
|
anchors.fill: parent
|
|
onDropped: (drop) => {
|
|
// TODO: forward to C++ if needed
|
|
// console.log("Dropped:", drop.urls)
|
|
}
|
|
}
|
|
|
|
function isPointOutsideWindow(p) {
|
|
return (
|
|
p.x < 0 ||
|
|
p.y < 0 ||
|
|
p.x > win.contentItem.width ||
|
|
p.y > win.contentItem.height
|
|
)
|
|
}
|
|
|
|
function settingAsBool(value, fallback) {
|
|
if (typeof value === "boolean")
|
|
return value
|
|
if (typeof value === "number")
|
|
return value !== 0
|
|
if (typeof value === "string") {
|
|
const s = value.trim().toLowerCase()
|
|
if (s === "true" || s === "1" || s === "yes" || s === "on")
|
|
return true
|
|
if (s === "false" || s === "0" || s === "no" || s === "off" || s === "")
|
|
return false
|
|
}
|
|
return fallback === undefined ? false : settingAsBool(fallback, false)
|
|
}
|
|
|
|
function indexFromValue(cb, v) {
|
|
if (!cb || v === undefined || v === null)
|
|
return -1
|
|
|
|
var sv = String(v)
|
|
|
|
for (var i = 0; i < cb.count; ++i) {
|
|
var it = (cb.model && cb.model.get) ? cb.model.get(i) : cb.model[i]
|
|
if (!it)
|
|
continue
|
|
|
|
var iv = it.value
|
|
if (iv === v || String(iv) === sv)
|
|
return i
|
|
}
|
|
return -1
|
|
}
|
|
|
|
function controlSpec(name, fallbackMin, fallbackMax) {
|
|
var spec = mainControlSpecs[name]
|
|
return spec ? spec : { "available": false, "min": fallbackMin, "max": fallbackMax }
|
|
}
|
|
|
|
function controlAvailable(name) {
|
|
return Boolean(mainControlSpecs[name] ?? false)
|
|
}
|
|
|
|
function rangeControlVisible(name) {
|
|
var spec = mainControlSpecs[name]
|
|
return Boolean(spec && (spec.available ?? false) && (spec.visible ?? true))
|
|
}
|
|
|
|
function anyControlVisible(names) {
|
|
for (var i = 0; i < names.length; ++i) {
|
|
if (controlAvailable(names[i]))
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
function activeReceiverItem() {
|
|
for (var i = 0; i < receiversRepeater.count; ++i) {
|
|
var row = receiversRepeater.itemAt(i)
|
|
if (row && row.receiverController && row.receiverController.active && row.receiverItem)
|
|
return row.receiverItem
|
|
}
|
|
var firstRow = receiversRepeater.count > 0 ? receiversRepeater.itemAt(0) : null
|
|
return firstRow ? firstRow.receiverItem : null
|
|
}
|
|
|
|
function shouldConfirmUnsavedSettings() {
|
|
if (!MainController || !MainController.settings)
|
|
return false
|
|
|
|
var confirm = MainController.settings.options["Interface.ConfirmSettingsChanged"]
|
|
return win.settingAsBool(MainController.settings.dirty, false)
|
|
&& (confirm === undefined || win.settingAsBool(confirm, true))
|
|
}
|
|
|
|
function applyWindowGeometry(g) {
|
|
if (g.valid) {
|
|
if (!win.waylandPlatform) {
|
|
win.x = g.x
|
|
win.y = g.y
|
|
}
|
|
win.width = Math.max(g.width, win.minimumWidth)
|
|
win.height = Math.max(g.height, win.minimumHeight)
|
|
if (g.maximized)
|
|
win.visibility = Window.Maximized
|
|
}
|
|
}
|
|
|
|
function rememberConnectedWindowGeometry() {
|
|
win.lastConnectedGeometry = {
|
|
valid: true,
|
|
x: win.x,
|
|
y: win.y,
|
|
width: win.width,
|
|
height: win.height,
|
|
maximized: win.visibility === Window.Maximized
|
|
}
|
|
}
|
|
|
|
property bool dialogExpandedWindow: false
|
|
|
|
function ensureWindowFitsPopup(popup) {
|
|
var pad = 40
|
|
var needW = popup.width + pad
|
|
var needH = popup.height + pad
|
|
if (win.width < needW || win.height < needH) {
|
|
win.dialogExpandedWindow = true
|
|
win.width = Math.max(win.width, needW)
|
|
win.height = Math.max(win.height, needH)
|
|
}
|
|
}
|
|
|
|
function shrinkAfterDialog() {
|
|
if (win.dialogExpandedWindow) {
|
|
win.dialogExpandedWindow = false
|
|
if (!win.radioConnected)
|
|
shrinkDisconnectedWindow()
|
|
}
|
|
}
|
|
|
|
function shrinkDisconnectedWindow() {
|
|
if (win.radioConnected || win.waylandPlatform)
|
|
return
|
|
if (win.visibility === Window.Maximized || win.visibility === Window.FullScreen)
|
|
win.visibility = Window.Windowed
|
|
win.width = win.minimumWidth
|
|
win.height = win.minimumHeight
|
|
}
|
|
|
|
function restoreConnectedWindowGeometry() {
|
|
if (win.waylandPlatform) {
|
|
win.connectedGeometryRestored = true
|
|
win.startupGeometryPending = false
|
|
return
|
|
}
|
|
if (win.connectedGeometryRestored)
|
|
return
|
|
|
|
var g = win.lastConnectedGeometry && win.lastConnectedGeometry.valid ? win.lastConnectedGeometry
|
|
: win.savedStartupGeometry
|
|
if (g && g.valid) {
|
|
win.savedStartupGeometry = g
|
|
win.startupGeometryPending = true
|
|
win.startupGeometryRestoreAttempts = 0
|
|
win.scheduleStartupGeometryRestore()
|
|
} else {
|
|
win.connectedGeometryRestored = true
|
|
}
|
|
}
|
|
|
|
function restoreWindowGeometry() {
|
|
if (win.waylandPlatform) {
|
|
win.visible = true
|
|
win.connectedGeometryRestored = true
|
|
win.startupGeometryPending = false
|
|
win.wasRadioConnected = win.radioConnected
|
|
return
|
|
}
|
|
|
|
var g = MainController.restoredMainWindowGeometry()
|
|
win.savedStartupGeometry = g
|
|
win.visible = true
|
|
if (win.radioConnected)
|
|
win.restoreConnectedWindowGeometry()
|
|
else
|
|
Qt.callLater(win.shrinkDisconnectedWindow)
|
|
win.wasRadioConnected = win.radioConnected
|
|
}
|
|
|
|
function saveWindowGeometry() {
|
|
if (!win.radioConnected || win.waylandPlatform)
|
|
return
|
|
MainController.saveMainWindowGeometry(win.x, win.y, win.width, win.height,
|
|
win.visibility === Window.Maximized)
|
|
}
|
|
|
|
function requestQuit() {
|
|
if (shouldConfirmUnsavedSettings()) {
|
|
unsavedSettingsDialog.showDialog()
|
|
return
|
|
}
|
|
|
|
win.close()
|
|
}
|
|
|
|
function finishQuit(saveFirst) {
|
|
if (saveFirst && MainController && MainController.settings)
|
|
MainController.settings.save()
|
|
|
|
win.quitConfirmed = true
|
|
win.close()
|
|
}
|
|
|
|
function shutdownAndQuit() {
|
|
if (win.shutdownStarted)
|
|
return
|
|
|
|
win.shutdownStarted = true
|
|
win.quitConfirmed = true
|
|
saveWindowGeometry()
|
|
unsavedSettingsDialog.visible = false
|
|
if (settingsLoader.item)
|
|
settingsLoader.item.close()
|
|
if (memoriesLoader.item) {
|
|
memoriesLoader.item.forceClose = true
|
|
memoriesLoader.item.close()
|
|
}
|
|
if (repeaterLoader.item) {
|
|
repeaterLoader.item.forceClose = true
|
|
repeaterLoader.item.close()
|
|
}
|
|
if (selectRadioLoader.item) {
|
|
MainController.selectRadio.visible = false
|
|
selectRadioLoader.item.close()
|
|
}
|
|
if (cwSenderLoader.item) {
|
|
MainController.cwSender.visible = false
|
|
cwSenderLoader.item.close()
|
|
}
|
|
for (var i = 0; i < rigCreatorWindows.length; ++i) {
|
|
if (rigCreatorWindows[i]) {
|
|
rigCreatorWindows[i].forceClose = true
|
|
rigCreatorWindows[i].close()
|
|
}
|
|
}
|
|
if (txAudioProcessingWindow)
|
|
txAudioProcessingWindow.close()
|
|
if (rxAudioProcessingWindow)
|
|
rxAudioProcessingWindow.close()
|
|
|
|
Qt.callLater(function() {
|
|
MainController.quitApplication()
|
|
})
|
|
}
|
|
|
|
Component {
|
|
id: rigCreatorComponent
|
|
RigCreator { }
|
|
}
|
|
|
|
Component {
|
|
id: txAudioProcessingComponent
|
|
AudioProcessing {
|
|
mode: "tx"
|
|
controller: MainController.settings
|
|
onClosing: function(close) {
|
|
win.txAudioProcessingWindow = null
|
|
destroy()
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: rxAudioProcessingComponent
|
|
AudioProcessing {
|
|
mode: "rx"
|
|
controller: MainController.settings
|
|
onClosing: function(close) {
|
|
win.rxAudioProcessingWindow = null
|
|
destroy()
|
|
}
|
|
}
|
|
}
|
|
|
|
function showTxAudioProcessing() {
|
|
MainController.ensureAudioProcessors()
|
|
if (!win.txAudioProcessingWindow)
|
|
win.txAudioProcessingWindow = txAudioProcessingComponent.createObject(null)
|
|
if (win.txAudioProcessingWindow) {
|
|
win.txAudioProcessingWindow.show()
|
|
win.txAudioProcessingWindow.raise()
|
|
win.txAudioProcessingWindow.requestActivate()
|
|
}
|
|
}
|
|
|
|
function showRxAudioProcessing() {
|
|
MainController.ensureAudioProcessors()
|
|
if (!win.rxAudioProcessingWindow)
|
|
win.rxAudioProcessingWindow = rxAudioProcessingComponent.createObject(null)
|
|
if (win.rxAudioProcessingWindow) {
|
|
win.rxAudioProcessingWindow.show()
|
|
win.rxAudioProcessingWindow.raise()
|
|
win.rxAudioProcessingWindow.requestActivate()
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: mainLayout
|
|
anchors.top: parent.top
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.topMargin: win.contentTopPadding
|
|
anchors.leftMargin: win.contentHorizontalPadding
|
|
anchors.rightMargin: win.contentHorizontalPadding
|
|
spacing: 6
|
|
|
|
// -------- 1) scopeVFOGroup (top) --------
|
|
Frame {
|
|
id: scopeVFOGroup
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
visible: win.radioConnected
|
|
//Layout.preferredHeight: implicitHeight // content drives height
|
|
padding: 0
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
id: receiversRepeater
|
|
model: MainController.receiverCount
|
|
|
|
delegate: Item {
|
|
id: row
|
|
Layout.fillWidth: true
|
|
|
|
Item { id: attachedHost; anchors.fill: parent }
|
|
|
|
Rectangle {
|
|
anchors.fill: attachedHost
|
|
anchors.margins: 1
|
|
color: "transparent"
|
|
border.width: 1
|
|
border.color: "red"
|
|
enabled: false
|
|
visible: row.receiverController && row.receiverController.active && !row.detached
|
|
z: 10
|
|
}
|
|
|
|
property int pendingX: 0
|
|
property int pendingY: 0
|
|
property bool havePendingPos: false
|
|
property bool detached: MainController.isReceiverDetached(index)
|
|
property bool detachedWindowActive: false
|
|
readonly property var receiverController: MainController.receiver(index)
|
|
readonly property var receiverItem: rxLoader.item
|
|
readonly property bool receiverVisible: receiverController
|
|
&& (receiverController.active
|
|
|| MainController.dualScope)
|
|
|
|
visible: !detached && receiverVisible
|
|
Layout.fillHeight: !detached && receiverVisible
|
|
Layout.preferredHeight: (detached || !receiverVisible) ? 0 : -1
|
|
Layout.minimumHeight: (detached || !receiverVisible) ? 0 : 240
|
|
|
|
clip: true
|
|
|
|
Behavior on Layout.preferredHeight {
|
|
NumberAnimation { duration: 120 } // tweak to taste
|
|
}
|
|
|
|
Loader {
|
|
id: rxLoader
|
|
active: true
|
|
sourceComponent: Receiver {
|
|
receiverIndex: index
|
|
controller: row.receiverController
|
|
}
|
|
onLoaded: {
|
|
rxLoader.item.parent = row.detached ? detachedHost : attachedHost
|
|
rxLoader.item.anchors.fill = row.detached ? detachedHost : attachedHost
|
|
rxLoader.item.anchors.margins = 1
|
|
detachedWin.applyWindowMode()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: rxLoader.item
|
|
function onRequestDetach(p) {
|
|
Logging.info("onRequestDetach(p) " + p.x + "," + p.y)
|
|
if (MainController.isReceiverDetached(index))
|
|
return
|
|
|
|
if (win.isPointOutsideWindow(p)) {
|
|
const g = win.contentItem.mapToGlobal(p.x, p.y)
|
|
row.pendingX = Math.round(g.x - 40)
|
|
row.pendingY = Math.round(g.y - 16)
|
|
row.havePendingPos = true
|
|
|
|
Logging.info("detach gpos " + g.x + "," + g.y)
|
|
MainController.setReceiverDetached(index, true)
|
|
}
|
|
}
|
|
function onFullScreenRequested(enabled) {
|
|
detachedWin.fullScreen = enabled
|
|
MainController.settings.saveReceiverSetting(index, "DetachedFullScreen", enabled)
|
|
if (enabled && !MainController.isReceiverDetached(index))
|
|
MainController.setReceiverDetached(index, true)
|
|
detachedWin.applyWindowMode()
|
|
}
|
|
function onSpectrumProcessingTime(ms) {
|
|
if (index === 0)
|
|
MainController.selectRadio.spectrumTime(ms)
|
|
}
|
|
function onWaterfallProcessingTime(ms) {
|
|
if (index === 0)
|
|
MainController.selectRadio.waterfallTime(ms)
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: MainController
|
|
function onReceiverDetachedChanged(i, d) {
|
|
if (i !== index) return
|
|
row.detached = d
|
|
detachedWin.applyWindowMode()
|
|
}
|
|
}
|
|
|
|
ApplicationWindow {
|
|
id: detachedWin
|
|
transientParent: null
|
|
title: qsTr("Receiver %1").arg(index + 1)
|
|
width: Number(MainController.settings.receiverSetting(index, "DetachedWidth", 900))
|
|
height: Number(MainController.settings.receiverSetting(index, "DetachedHeight", 500))
|
|
color: palette.window
|
|
property bool restoringGeometry: true
|
|
property bool fullScreen: win.settingAsBool(MainController.settings.receiverSetting(index, "DetachedFullScreen", false), false)
|
|
onActiveChanged: row.detachedWindowActive = active
|
|
|
|
function saveDetachedGeometry() {
|
|
if (restoringGeometry || !row.detached || fullScreen)
|
|
return
|
|
MainController.settings.saveReceiverSetting(index, "DetachedX", Math.round(x))
|
|
MainController.settings.saveReceiverSetting(index, "DetachedY", Math.round(y))
|
|
MainController.settings.saveReceiverSetting(index, "DetachedWidth", Math.round(width))
|
|
MainController.settings.saveReceiverSetting(index, "DetachedHeight", Math.round(height))
|
|
}
|
|
|
|
function applyWindowMode() {
|
|
visibility = row.detached ? (fullScreen ? Window.FullScreen : Window.Windowed) : Window.Hidden
|
|
}
|
|
|
|
function applyDisabledPalette() {
|
|
if (win._sysTheme) return
|
|
try {
|
|
palette.disabled.window = MainController.settings.options["Color.Window"]
|
|
palette.disabled.windowText = Qt.darker(MainController.settings.options["Color.WindowText"], 2.5)
|
|
palette.disabled.base = Qt.darker(MainController.settings.options["Color.Base"], 1.1)
|
|
palette.disabled.alternateBase = Qt.darker(MainController.settings.options["Color.AlternateBase"], 1.1)
|
|
palette.disabled.text = Qt.darker(MainController.settings.options["Color.MainText"], 2.5)
|
|
palette.disabled.button = Qt.darker(MainController.settings.options["Color.Button"], 1.3)
|
|
palette.disabled.buttonText = Qt.darker(MainController.settings.options["Color.ButtonText"], 2.5)
|
|
palette.disabled.brightText = Qt.darker(MainController.settings.options["Color.BrightText"], 2.5)
|
|
palette.disabled.highlight = Qt.darker(MainController.settings.options["Color.Highlight"], 1.5)
|
|
palette.disabled.highlightedText = Qt.darker(MainController.settings.options["Color.MainText"], 2.5)
|
|
palette.disabled.mid = MainController.settings.options["Color.Mid"]
|
|
palette.disabled.dark = MainController.settings.options["Color.Dark"]
|
|
palette.disabled.light = Qt.darker(MainController.settings.options["Color.Light"], 1.2)
|
|
palette.disabled.placeholderText = Qt.darker(MainController.settings.options["Color.PlaceholderText"], 1.5)
|
|
} catch (e) {
|
|
// Qt 5 does not expose palette disabled groups to QML.
|
|
}
|
|
}
|
|
|
|
palette {
|
|
window: win._sysTheme ? undefined : MainController.settings.options["Color.Window"]
|
|
windowText: win._sysTheme ? undefined : MainController.settings.options["Color.WindowText"]
|
|
base: win._sysTheme ? undefined : MainController.settings.options["Color.Base"]
|
|
alternateBase: win._sysTheme ? undefined : MainController.settings.options["Color.AlternateBase"]
|
|
text: win._sysTheme ? undefined : MainController.settings.options["Color.MainText"]
|
|
button: win._sysTheme ? undefined : MainController.settings.options["Color.Button"]
|
|
buttonText: win._sysTheme ? undefined : MainController.settings.options["Color.ButtonText"]
|
|
brightText: win._sysTheme ? undefined : MainController.settings.options["Color.BrightText"]
|
|
highlight: win._sysTheme ? undefined : MainController.settings.options["Color.Highlight"]
|
|
highlightedText: win._sysTheme ? undefined : MainController.settings.options["Color.HighlightedText"]
|
|
mid: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
dark: win._sysTheme ? undefined : MainController.settings.options["Color.Dark"]
|
|
light: win._sysTheme ? undefined : MainController.settings.options["Color.Light"]
|
|
|
|
disabled {
|
|
window: win._sysTheme ? undefined : MainController.settings.options["Color.Window"]
|
|
windowText: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
base: win._sysTheme ? undefined : MainController.settings.options["Color.Base"]
|
|
alternateBase: win._sysTheme ? undefined : MainController.settings.options["Color.AlternateBase"]
|
|
text: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
button: win._sysTheme ? undefined : MainController.settings.options["Color.Button"]
|
|
buttonText: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
brightText: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
highlight: win._sysTheme ? undefined : MainController.settings.options["Color.Dark"]
|
|
highlightedText: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
mid: win._sysTheme ? undefined : MainController.settings.options["Color.Mid"]
|
|
dark: win._sysTheme ? undefined : MainController.settings.options["Color.Dark"]
|
|
light: win._sysTheme ? undefined : MainController.settings.options["Color.Light"]
|
|
}
|
|
}
|
|
|
|
Item { id: detachedHost; anchors.fill: parent }
|
|
|
|
Rectangle {
|
|
anchors.fill: detachedHost
|
|
anchors.margins: 1
|
|
color: "transparent"
|
|
border.width: 1
|
|
border.color: "red"
|
|
enabled: false
|
|
visible: row.receiverController && row.receiverController.active
|
|
z: 10
|
|
}
|
|
|
|
onVisibleChanged: function() {
|
|
if (!rxLoader.item) return
|
|
|
|
rxLoader.item.anchors.fill = undefined
|
|
rxLoader.item.parent = visible ? detachedHost : attachedHost
|
|
rxLoader.item.anchors.fill = visible ? detachedHost : attachedHost
|
|
rxLoader.item.anchors.margins = 1
|
|
Qt.callLater(MainController.updateApplicationPalette)
|
|
Qt.callLater(detachedWin.applyDisabledPalette)
|
|
Qt.callLater(detachedWin.applyWindowMode)
|
|
|
|
if (visible && row.havePendingPos) {
|
|
Qt.callLater(function() {
|
|
if (!win.waylandPlatform) {
|
|
detachedWin.x = row.pendingX
|
|
detachedWin.y = row.pendingY
|
|
}
|
|
row.havePendingPos = false
|
|
})
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
if (!win.waylandPlatform) {
|
|
x = Number(MainController.settings.receiverSetting(index, "DetachedX", x))
|
|
y = Number(MainController.settings.receiverSetting(index, "DetachedY", y))
|
|
}
|
|
restoringGeometry = false
|
|
applyDisabledPalette()
|
|
Qt.callLater(detachedWin.applyWindowMode)
|
|
}
|
|
|
|
Connections {
|
|
target: MainController.settings
|
|
function onColChanged(items) {
|
|
detachedWin.applyDisabledPalette()
|
|
}
|
|
}
|
|
|
|
onXChanged: saveDetachedGeometry()
|
|
onYChanged: saveDetachedGeometry()
|
|
onWidthChanged: saveDetachedGeometry()
|
|
onHeightChanged: saveDetachedGeometry()
|
|
|
|
onClosing: function(close) {
|
|
MainController.setReceiverDetached(index, false)
|
|
close.accepted = true
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// -------- 2) mainGroup (middle, fixed-ish) --------
|
|
Frame {
|
|
id: mainGroup
|
|
Layout.fillWidth: true
|
|
property real targetHeight: Math.max(leftControlsColumn.implicitHeight, mainControlsFlow.implicitHeight)
|
|
Layout.preferredHeight: targetHeight
|
|
clip: true
|
|
padding: 3
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
spacing: win.mainControlSpacing
|
|
|
|
ColumnLayout {
|
|
id: leftControlsColumn
|
|
Layout.preferredWidth: 320
|
|
Layout.maximumWidth: 320
|
|
Layout.fillHeight: true
|
|
spacing: win.mainControlSpacing
|
|
|
|
ColumnLayout {
|
|
spacing: 1
|
|
visible: win.radioConnected
|
|
|
|
Repeater {
|
|
model: [2, 3]
|
|
OptionalMeterSlot {
|
|
slot: modelData
|
|
width: win.optionalMeterWidth
|
|
height: win.optionalMeterHeight
|
|
Layout.preferredWidth: win.optionalMeterWidth
|
|
Layout.preferredHeight: win.optionalMeterHeight
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillHeight: true }
|
|
|
|
RowLayout {
|
|
id: powerButtonsRow
|
|
spacing: win.mainControlSpacing
|
|
Button { text: qsTr("Power On"); onClicked: MainController.powerOn() }
|
|
Button { text: qsTr("Power Off"); onClicked: MainController.powerOff() }
|
|
}
|
|
}
|
|
|
|
Flow {
|
|
id: mainControlsFlow
|
|
Layout.fillWidth: true
|
|
Layout.alignment: Qt.AlignTop
|
|
spacing: win.flowSpacing
|
|
onWidthChanged: win.updateFlowLayout()
|
|
|
|
// ---- tuningLayout ----
|
|
ColumnLayout {
|
|
width: win.mainControlDialSize
|
|
Layout.alignment: Qt.AlignTop
|
|
Layout.preferredWidth: win.mainControlDialSize
|
|
Layout.maximumWidth: win.mainControlDialSize
|
|
spacing: win.mainControlSpacing
|
|
visible: win.radioConnected
|
|
|
|
Dial {
|
|
id: freqDial
|
|
property real lastValue: value
|
|
property bool syncing: false
|
|
property double lastMoveTime: 0
|
|
property int lastDirection: 0
|
|
property int fastMoveCount: 0
|
|
Layout.preferredWidth: win.mainControlDialSize
|
|
Layout.preferredHeight: win.mainControlDialSize
|
|
from: 3000
|
|
to: 4000
|
|
stepSize: 10
|
|
wrap: true
|
|
visible: win.radioConnected
|
|
enabled: firstReceiver !== null
|
|
onMoved: {
|
|
if (syncing || !firstReceiver)
|
|
return
|
|
|
|
const fullSweep = to - from
|
|
var delta = value - lastValue
|
|
if (delta > fullSweep / 2)
|
|
delta -= fullSweep
|
|
else if (delta < -fullSweep / 2)
|
|
delta += fullSweep
|
|
|
|
var direction = 0
|
|
if (Math.abs(delta) < stepSize)
|
|
direction = delta > 0 ? 1 : -1
|
|
else
|
|
direction = Math.round(delta / stepSize) > 0 ? 1 : -1
|
|
|
|
if (direction !== 0) {
|
|
const now = Date.now()
|
|
const elapsed = lastMoveTime > 0 ? Math.max(1, now - lastMoveTime) : 0
|
|
var multiplier = 1
|
|
|
|
if (direction === lastDirection && elapsed > 0 && elapsed <= 90)
|
|
fastMoveCount += 1
|
|
else
|
|
fastMoveCount = 0
|
|
|
|
if (fastMoveCount >= 5 && elapsed <= 35)
|
|
multiplier = 10
|
|
else if (fastMoveCount >= 3 && elapsed <= 60)
|
|
multiplier = 5
|
|
else if (fastMoveCount >= 2 && elapsed <= 90)
|
|
multiplier = 2
|
|
|
|
firstReceiver.tuneSteps(direction * multiplier, 0, true)
|
|
lastDirection = direction
|
|
lastMoveTime = now
|
|
}
|
|
|
|
lastValue = value
|
|
}
|
|
Component.onCompleted: {
|
|
lastValue = value
|
|
lastMoveTime = 0
|
|
lastDirection = 0
|
|
fastMoveCount = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
Frame {
|
|
id: tuningControlsGroup
|
|
width: 120
|
|
Layout.alignment: Qt.AlignTop
|
|
Layout.preferredWidth: 120
|
|
Layout.maximumWidth: 120
|
|
padding: 3
|
|
visible: win.radioConnected
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 4
|
|
|
|
ComboBox {
|
|
id: tuningStepCombo
|
|
Layout.fillWidth: true
|
|
readonly property var spec: MainController ? MainController.uiSpecs["tuningSteps"] : null
|
|
model: spec ? spec.model : []
|
|
textRole: spec ? spec.textRole : "text"
|
|
valueRole: spec ? spec.valueRole : "value"
|
|
visible: win.radioConnected && (spec ? (spec.visible ?? true) : false)
|
|
currentIndex: MainController ? indexFromValue(tuningStepCombo, MainController.stepSize) : -1
|
|
onActivated: MainController.stepSize = currentValue
|
|
}
|
|
|
|
CheckBox {
|
|
id: tuneLockChk
|
|
text: qsTr("F Lock")
|
|
visible: win.radioConnected
|
|
onToggled: MainController.setFrequencyLock(checked)
|
|
}
|
|
|
|
RowLayout {
|
|
id: ritControlsRow
|
|
visible: win.radioConnected && (mainControlSpecs.canRit ?? false)
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: win.ritDialSize + 8
|
|
Layout.preferredHeight: win.ritDialSize + 8
|
|
radius: width / 2
|
|
color: Qt.rgba(win.palette.button.r, win.palette.button.g, win.palette.button.b, 0.35)
|
|
border.color: win.palette.mid
|
|
|
|
Dial {
|
|
id: ritTuneDial
|
|
anchors.centerIn: parent
|
|
width: win.ritDialSize
|
|
height: win.ritDialSize
|
|
readonly property var spec: controlSpec("ritFrequency", -500, 500)
|
|
from: spec.min
|
|
to: spec.max
|
|
value: MainController.ritFrequency
|
|
enabled: (mainControlSpecs.canRit ?? false) && MainController.ritEnabled
|
|
stepSize: 10
|
|
wrap: false
|
|
onMoved: MainController.ritFrequency = Math.round(value)
|
|
|
|
HoverHandler { id: hoverRitTune }
|
|
ToolTip.visible: hoverRitTune.hovered
|
|
ToolTip.text: qsTr("RIT") + ": " + Math.round(value).toString() + " Hz"
|
|
ToolTip.delay: 300
|
|
}
|
|
}
|
|
CheckBox {
|
|
id: ritEnableChk
|
|
text: qsTr("RIT")
|
|
checked: MainController.ritEnabled
|
|
enabled: mainControlSpecs.canRit ?? false
|
|
onToggled: MainController.ritEnabled = checked
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- levelsHorizontalLayout (RF/AF/SQ/Mic/TX/Mon sliders) + OtherControls ----
|
|
ColumnLayout {
|
|
id: levelsControlsGroup
|
|
width: implicitWidth
|
|
Layout.alignment: Qt.AlignTop
|
|
Layout.fillWidth: false
|
|
Layout.preferredWidth: implicitWidth
|
|
spacing: win.mainControlSpacing
|
|
visible: win.radioConnected
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 10
|
|
|
|
ColumnLayout {
|
|
visible: win.radioConnected && MainController.localAudioAvailable
|
|
enabled: MainController.localAudioAvailable
|
|
RowLayout {
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
spacing: 4
|
|
|
|
Slider {
|
|
id: volumeSlider
|
|
from: 0
|
|
to: 255
|
|
value: MainController.localAfGain
|
|
enabled: MainController.localAudioAvailable
|
|
orientation: Qt.Vertical
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
onMoved: MainController.localAfGain = Math.round(value)
|
|
|
|
HoverHandler { id: hoverVolume }
|
|
ToolTip.visible: hoverVolume.hovered
|
|
ToolTip.text: Math.round((value - from) / Math.max(1, to - from) * 100).toString() + " %"
|
|
ToolTip.delay: 300
|
|
}
|
|
|
|
AudioLevelBar {
|
|
value: MainController.rxAudioLevel
|
|
label: qsTr("RX audio")
|
|
active: MainController.localAudioAvailable
|
|
}
|
|
}
|
|
Label { text: qsTr("Vol"); horizontalAlignment: Text.AlignHCenter }
|
|
}
|
|
ColumnLayout {
|
|
visible: rangeControlVisible("txPower")
|
|
Slider {
|
|
id: txPowerSlider
|
|
readonly property var spec: controlSpec("txPower", 0, 255)
|
|
from: spec.min
|
|
to: spec.max
|
|
value: MainController.txPower
|
|
enabled: spec.available ?? false
|
|
orientation: Qt.Vertical
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
onMoved: MainController.txPower = Math.round(value)
|
|
|
|
HoverHandler { id: hoverTxPower }
|
|
ToolTip.visible: hoverTxPower.hovered
|
|
ToolTip.text: Math.round((value - from) / Math.max(1, to - from) * 100).toString() + " %"
|
|
ToolTip.delay: 300
|
|
}
|
|
Label { text: qsTr("TX"); horizontalAlignment: Text.AlignHCenter }
|
|
}
|
|
ColumnLayout {
|
|
visible: rangeControlVisible("monitorGain") || (mainControlSpecs.canMonitor ?? false)
|
|
Slider {
|
|
id: monitorSlider
|
|
readonly property var spec: controlSpec("monitorGain", 0, 255)
|
|
from: spec.min
|
|
to: spec.max
|
|
value: MainController.monitorGain
|
|
enabled: spec.available ?? false
|
|
orientation: Qt.Vertical
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
onMoved: MainController.monitorGain = Math.round(value)
|
|
|
|
HoverHandler { id: hoverMonitor }
|
|
ToolTip.visible: hoverMonitor.hovered
|
|
ToolTip.text: Math.round((value - from) / Math.max(1, to - from) * 100).toString() + " %"
|
|
ToolTip.delay: 300
|
|
}
|
|
Label {
|
|
id: monitorLabel
|
|
text: qsTr("Mon")
|
|
horizontalAlignment: Text.AlignHCenter
|
|
enabled: mainControlSpecs.canMonitor ?? false
|
|
font.bold: MainController.monitorEnabled
|
|
font.strikeout: !MainController.monitorEnabled
|
|
opacity: enabled ? 1.0 : 0.45
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: monitorLabel.enabled
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: MainController.monitorEnabled = !MainController.monitorEnabled
|
|
}
|
|
}
|
|
}
|
|
ColumnLayout {
|
|
visible: rangeControlVisible("micGain")
|
|
RowLayout {
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
spacing: 4
|
|
|
|
Slider {
|
|
id: micGainSlider
|
|
readonly property var spec: controlSpec("micGain", 0, 255)
|
|
from: MainController.modGainMin
|
|
to: MainController.modGainMax
|
|
value: MainController.micGain
|
|
enabled: spec.available ?? false
|
|
orientation: Qt.Vertical
|
|
Layout.preferredHeight: win.mainControlSliderHeight
|
|
onMoved: MainController.micGain = Math.round(value)
|
|
|
|
HoverHandler { id: hoverMicGain }
|
|
ToolTip.visible: hoverMicGain.hovered
|
|
ToolTip.text: Math.round((value - from) / Math.max(1, to - from) * 100).toString() + " %"
|
|
ToolTip.delay: 300
|
|
}
|
|
|
|
AudioLevelBar {
|
|
value: MainController.txAudioLevel
|
|
label: qsTr("TX audio")
|
|
active: micGainSlider.enabled
|
|
}
|
|
}
|
|
Label { text: MainController.modGainLabel; horizontalAlignment: Text.AlignHCenter }
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- command buttons (Transmit, ATU, etc.) ----
|
|
// Fixed width so column changes don't cause the Flow to re-wrap.
|
|
Item {
|
|
id: commandButtonsWrapper
|
|
width: 246
|
|
height: commandButtonsColumn.implicitHeight
|
|
clip: true
|
|
visible: win.radioConnected
|
|
|
|
Behavior on height {
|
|
enabled: win._flowLayoutInitialized
|
|
NumberAnimation { duration: 200; easing.type: Easing.OutQuad }
|
|
}
|
|
|
|
GridLayout {
|
|
id: commandButtonsColumn
|
|
property bool twoColumns: win.cmdTwoColumns
|
|
columns: twoColumns ? 2 : 1
|
|
anchors.left: parent.left
|
|
rowSpacing: win.mainControlSpacing
|
|
columnSpacing: win.mainControlSpacing
|
|
|
|
Button {
|
|
text: MainController.transmitting ? qsTr("Receive") : qsTr("Transmit")
|
|
Layout.preferredWidth: 120
|
|
Layout.preferredHeight: 50
|
|
checkable: true
|
|
checked: MainController.transmitting
|
|
enabled: mainControlSpecs.canTransmit ?? false
|
|
visible: mainControlSpecs.canTransmit ?? false
|
|
onClicked: MainController.toggleTransmit()
|
|
}
|
|
CheckBox {
|
|
text: qsTr("Enable ATU")
|
|
Layout.preferredWidth: 120
|
|
checked: MainController.tunerEnabled
|
|
enabled: mainControlSpecs.canTune ?? false
|
|
visible: mainControlSpecs.canTune ?? false
|
|
onToggled: MainController.tunerEnabled = checked
|
|
}
|
|
Button {
|
|
text: qsTr("Tune")
|
|
Layout.preferredWidth: 120
|
|
enabled: mainControlSpecs.canTune ?? false
|
|
visible: mainControlSpecs.canTune ?? false
|
|
onClicked: MainController.tuneNow()
|
|
}
|
|
Button {
|
|
text: qsTr("CW")
|
|
Layout.preferredWidth: 120
|
|
enabled: mainControlSpecs.canSendCW ?? false
|
|
visible: mainControlSpecs.canSendCW ?? false
|
|
onClicked: win.openCwSenderWindow()
|
|
}
|
|
|
|
Button {
|
|
text: qsTr("Rpt/Split")
|
|
Layout.preferredWidth: 120
|
|
enabled: win.radioConnected && (mainControlSpecs.canRepeater ?? false)
|
|
visible: win.radioConnected && (mainControlSpecs.canRepeater ?? false)
|
|
onClicked: win.openRepeaterWindow()
|
|
}
|
|
Button {
|
|
text: qsTr("Memories")
|
|
Layout.preferredWidth: 120
|
|
visible: win.radioConnected && (mainControlSpecs.canMemories ?? false)
|
|
enabled: win.radioConnected && (mainControlSpecs.canMemories ?? false)
|
|
onClicked: win.openMemoriesWindow()
|
|
}
|
|
}
|
|
} // commandButtonsWrapper
|
|
|
|
Flow {
|
|
id: scopeAndOtherControlsFlow
|
|
readonly property bool hasReceiverScopeControls: (mainControlSpecs.canDualScope ?? false)
|
|
|| (mainControlSpecs.canDualWatch ?? false)
|
|
|| (mainControlSpecs.canMainSub ?? false)
|
|
|| (mainControlSpecs.canSwapMainSub ?? false)
|
|
|| (mainControlSpecs.canEqualMainSub ?? false)
|
|
readonly property bool showScopeControls: win.radioConnected && hasReceiverScopeControls
|
|
readonly property bool showOtherControls: win.radioConnected && anyControlVisible(["canCompressor", "canVox"])
|
|
property int scopePaneWidth: win.scopeFlowPaneWidth
|
|
readonly property int otherPaneWidth: 165
|
|
readonly property int rowWidthBeforeScope: (freqDial.visible ? win.mainControlDialSize : 0)
|
|
+ (tuningControlsGroup.visible ? tuningControlsGroup.width : 0)
|
|
+ (levelsControlsGroup.visible ? levelsControlsGroup.implicitWidth : 0)
|
|
+ (commandButtonsWrapper.visible ? commandButtonsWrapper.width : 0)
|
|
+ (4 * mainControlsFlow.spacing)
|
|
readonly property bool scopeFitsOnCurrentRow: mainControlsFlow.width >= (rowWidthBeforeScope + scopePaneWidth)
|
|
readonly property bool otherFitsBesideScopeOnCurrentRow: mainControlsFlow.width >= (rowWidthBeforeScope + scopePaneWidth + otherPaneWidth + spacing)
|
|
readonly property bool otherFitsBesideScopeOnWrappedRow: !scopeFitsOnCurrentRow
|
|
&& mainControlsFlow.width >= (scopePaneWidth + otherPaneWidth + spacing)
|
|
readonly property bool canPlaceOtherBesideScope: showScopeControls
|
|
&& showOtherControls
|
|
&& (otherFitsBesideScopeOnCurrentRow || otherFitsBesideScopeOnWrappedRow)
|
|
width: showScopeControls
|
|
? (canPlaceOtherBesideScope ? scopePaneWidth + otherPaneWidth + spacing : scopePaneWidth)
|
|
: otherPaneWidth
|
|
height: implicitHeight
|
|
spacing: win.mainControlSpacing
|
|
visible: showScopeControls || showOtherControls
|
|
|
|
GroupBox {
|
|
id: scopeSettingsGroup
|
|
title: qsTr("Scope Settings")
|
|
width: scopeAndOtherControlsFlow.scopePaneWidth
|
|
visible: scopeAndOtherControlsFlow.showScopeControls
|
|
|
|
contentItem: GridLayout {
|
|
columns: scopeAndOtherControlsFlow.scopePaneWidth >= 600 ? 6 : 3
|
|
rowSpacing: scopeAndOtherControlsFlow.scopePaneWidth >= 600 ? 10 : 6
|
|
columnSpacing: scopeAndOtherControlsFlow.scopePaneWidth >= 600 ? 10 : 6
|
|
|
|
Button {
|
|
text: qsTr("Dual Scope")
|
|
checkable: true
|
|
checked: MainController.dualScope
|
|
enabled: mainControlSpecs.canDualScope ?? false
|
|
visible: mainControlSpecs.canDualScope ?? false
|
|
onToggled: MainController.dualScope = checked
|
|
}
|
|
Button {
|
|
text: qsTr("Dual Watch")
|
|
checkable: true
|
|
checked: MainController.dualWatch
|
|
enabled: mainControlSpecs.canDualWatch ?? false
|
|
visible: mainControlSpecs.canDualWatch ?? false
|
|
onToggled: MainController.dualWatch = checked
|
|
}
|
|
Button {
|
|
text: qsTr("Split")
|
|
checkable: true
|
|
checked: MainController.splitEnabled
|
|
enabled: mainControlSpecs.canSplit ?? false
|
|
visible: (mainControlSpecs.canSplit ?? false) && scopeAndOtherControlsFlow.hasReceiverScopeControls
|
|
onToggled: MainController.splitEnabled = checked
|
|
}
|
|
|
|
Button {
|
|
text: qsTr("Main/Sub")
|
|
enabled: mainControlSpecs.canMainSub ?? false
|
|
visible: mainControlSpecs.canMainSub ?? false
|
|
onClicked: MainController.selectMainSub()
|
|
}
|
|
Button {
|
|
text: qsTr("Main<>Sub")
|
|
enabled: mainControlSpecs.canSwapMainSub ?? false
|
|
visible: mainControlSpecs.canSwapMainSub ?? false
|
|
onClicked: MainController.swapMainSub()
|
|
}
|
|
Button {
|
|
text: qsTr("Main=Sub")
|
|
enabled: mainControlSpecs.canEqualMainSub ?? false
|
|
visible: mainControlSpecs.canEqualMainSub ?? false
|
|
onClicked: MainController.equalizeMainSub()
|
|
}
|
|
}
|
|
}
|
|
|
|
Frame {
|
|
id: otherControlsFrame
|
|
width: scopeAndOtherControlsFlow.otherPaneWidth
|
|
padding: 3
|
|
visible: scopeAndOtherControlsFlow.showOtherControls
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 3
|
|
RowLayout {
|
|
CheckBox {
|
|
text: qsTr("CMP")
|
|
checked: MainController.compressorEnabled
|
|
enabled: mainControlSpecs.canCompressor ?? false
|
|
visible: mainControlSpecs.canCompressor ?? false
|
|
onToggled: MainController.compressorEnabled = checked
|
|
}
|
|
CheckBox {
|
|
text: qsTr("VOX")
|
|
checked: MainController.voxEnabled
|
|
enabled: mainControlSpecs.canVox ?? false
|
|
visible: mainControlSpecs.canVox ?? false
|
|
onToggled: MainController.voxEnabled = checked
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: win.radioConnected ? 0 : 1
|
|
height: 1
|
|
visible: !win.radioConnected
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------- 3) lowerButtonsFrame (bottom) --------
|
|
Frame {
|
|
id: lowerButtonsFrame
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: implicitHeight
|
|
padding: 1
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
spacing: 6
|
|
|
|
Button {
|
|
text: qsTr("About")
|
|
onClicked: {
|
|
win.ensureWindowFitsPopup(aboutDialog)
|
|
aboutDialog.open()
|
|
}
|
|
}
|
|
Button {
|
|
text: qsTr("Settings")
|
|
onClicked: showSettingsWindow()
|
|
}
|
|
Button {
|
|
text: qsTr("TX Audio Proc")
|
|
visible: win.radioConnected
|
|
onClicked: showTxAudioProcessing()
|
|
}
|
|
Button {
|
|
text: qsTr("RX Audio Proc")
|
|
visible: win.radioConnected
|
|
onClicked: showRxAudioProcessing()
|
|
}
|
|
|
|
Button {
|
|
text: qsTr("Radio Status")
|
|
onClicked: showSelectRadioWindow()
|
|
}
|
|
Button {
|
|
text: qsTr("Log")
|
|
onClicked: {
|
|
loggingWindow.show()
|
|
loggingWindow.raise()
|
|
loggingWindow.requestActivate()
|
|
}
|
|
}
|
|
Button {
|
|
text: qsTr("Rig Creator")
|
|
onClicked: {
|
|
const w = rigCreatorComponent.createObject(win)
|
|
if (w) {
|
|
const windows = rigCreatorWindows.slice()
|
|
windows.push(w)
|
|
rigCreatorWindows = windows
|
|
|
|
if (!win.waylandPlatform) {
|
|
const screenRect = win.screen ? win.screen.availableGeometry : null
|
|
const margin = 24
|
|
const offset = Math.min(40 + (windows.length - 1) * 24, 160)
|
|
if (screenRect) {
|
|
w.x = Math.max(screenRect.x + margin,
|
|
Math.min(win.x + offset,
|
|
screenRect.x + screenRect.width - w.width - margin))
|
|
w.y = Math.max(screenRect.y + margin,
|
|
Math.min(win.y + offset,
|
|
screenRect.y + screenRect.height - w.height - margin))
|
|
} else {
|
|
w.x = Math.max(0, win.x + offset)
|
|
w.y = Math.max(0, win.y + offset)
|
|
}
|
|
}
|
|
|
|
w.show()
|
|
w.requestActivate()
|
|
}
|
|
}
|
|
}
|
|
|
|
Item { Layout.fillWidth: true } // spacer (horizontalSpacer_32)
|
|
|
|
Button {
|
|
text: (connStatus === 0) ?
|
|
qsTr("Connect to Radio") :
|
|
(connStatus === 1) ?
|
|
qsTr("Cancel Connection") :
|
|
(connStatus === 2) ?
|
|
qsTr("Disconnect from Radio") : qsTr("Unknown status!")
|
|
onClicked: MainController.connectionHandler()
|
|
}
|
|
|
|
Item { Layout.fillWidth: true } // spacer (horizontalSpacer_9)
|
|
|
|
Button { text: qsTr("Exit Program"); onClicked: win.requestQuit() }
|
|
}
|
|
}
|
|
}
|
|
|
|
// If you want a QWidget-like status bar, do this:
|
|
footer: ToolBar {
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.leftMargin: win.contentHorizontalPadding
|
|
anchors.rightMargin: win.contentHorizontalPadding
|
|
spacing: 12
|
|
|
|
Label {
|
|
Layout.fillWidth: true
|
|
text: MainController ? MainController.footerMessageText : ""
|
|
color: win.palette.windowText
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignLeft
|
|
}
|
|
|
|
Label {
|
|
visible: win.radioConnected
|
|
Layout.preferredWidth: win.radioConnected ? 360 : 0
|
|
text: MainController ? MainController.radioStatusText : ""
|
|
color: win.palette.windowText
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignRight
|
|
}
|
|
|
|
Label {
|
|
visible: win.radioConnected
|
|
Layout.preferredWidth: win.radioConnected ? 90 : 0
|
|
text: MainController && MainController.rigModelName.length > 0 ? MainController.rigModelName : qsTr("NONE")
|
|
color: win.palette.windowText
|
|
font.bold: true
|
|
elide: Text.ElideRight
|
|
horizontalAlignment: Text.AlignRight
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Dialog {
|
|
id: aboutDialog
|
|
width: 700
|
|
height: 621
|
|
title: qsTr("About wfview")
|
|
modal: true
|
|
x: Math.round((win.width - width) / 2)
|
|
y: Math.round((win.height - height) / 2)
|
|
|
|
// Remove default padding to let AboutBox fill the dialog
|
|
padding: 0
|
|
|
|
background: Rectangle {
|
|
color: win.palette.window
|
|
border.color: win.palette.highlight
|
|
border.width: 1
|
|
radius: 4
|
|
}
|
|
|
|
contentItem: AboutBox {
|
|
// Set your build information here
|
|
wfviewVersion: MainController ? MainController.uiSpecs["program"].wfviewVersion : ""
|
|
gitShort: MainController ? MainController.uiSpecs["program"].gitShort : ""
|
|
buildDate: MainController ? MainController.uiSpecs["program"].buildDate : ""
|
|
buildTime: MainController ? MainController.uiSpecs["program"].buildTime : ""
|
|
buildUser: MainController ? MainController.uiSpecs["program"].buildUser : ""
|
|
buildHost: MainController ? MainController.uiSpecs["program"].buildHost : ""
|
|
qtVersion: Qt.version ? Qt.version : ""
|
|
}
|
|
|
|
standardButtons: Dialog.Close
|
|
onClosed: win.shrinkAfterDialog()
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
MainController.updateApplicationPalette();
|
|
restoreWindowGeometry()
|
|
// Check if this is the first run
|
|
//if (MainController.isFirstRun()) {
|
|
// firstTimeSetup.visible = true
|
|
//}
|
|
}
|
|
|
|
}
|