mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-25 15:45:58 +01:00
feat: improve manual QTF heading control
This commit is contained in:
1 parent
4be6c178f8
commit
1cda9b4ad9
29 files changed
+663
-19
No files matched your search
@@ -57,11 +57,13 @@ public class ChatPreferences {
|
||||
* only use is a best-effort warning when a file is newer than the application. Release
|
||||
* 1.50 added the two palette-override elements and deliberately left this at 7. A bump
|
||||
* that no reader acts on only invites the next person to bump it again.
|
||||
* Version 9 marks the manual-QTF-heading opt-out migration. A missing opt-out marker
|
||||
* enables the feature, and a subsequently written marker preserves the user's decision.
|
||||
* <p>
|
||||
* Reading must stay backwards compatible: missing/unknown tags should fall back to defaults.
|
||||
*/
|
||||
// private static final int CONFIG_VERSION = 2;
|
||||
public static final int CONFIG_VERSION = 8;
|
||||
public static final int CONFIG_VERSION = 9;
|
||||
|
||||
// Prefer writing tag names that mirror variable names (human readable). Keep legacy tags for compatibility.
|
||||
private static final String TAG_CONFIG_VERSION = "configVersion";
|
||||
@@ -202,6 +204,7 @@ public class ChatPreferences {
|
||||
int stn_on4kstServersPort = 23001;
|
||||
|
||||
boolean stn_pstRotatorEnabled = false;
|
||||
boolean stn_qtfFilterDirectionAsAntennaHeadingOptedOut;
|
||||
String stn_pstRotatorHost = "127.0.0.1";
|
||||
int stn_pstRotatorPort = 12000;
|
||||
boolean stn_pstRotator2Enabled = false;
|
||||
@@ -259,6 +262,7 @@ public class ChatPreferences {
|
||||
boolean loginToSecondChatEnabled;
|
||||
MutableValue<Double> actualQTF = new SimpleValue<>(360.0); // will be updated by user at runtime!
|
||||
MutableValue<Double> actualQTF2 = new SimpleValue<>(Double.NaN); // heading of rotator 2 (SO2R), NaN when unknown
|
||||
private boolean actualQtfInitializedFromPreferences;
|
||||
|
||||
boolean stn_bandActive50;
|
||||
boolean stn_bandActive70;
|
||||
@@ -1052,6 +1056,20 @@ public class ChatPreferences {
|
||||
this.stn_pstRotatorEnabled = stn_pstRotatorEnabled;
|
||||
}
|
||||
|
||||
public boolean isStn_useQtfFilterDirectionAsAntennaHeading() {
|
||||
return !stn_qtfFilterDirectionAsAntennaHeadingOptedOut;
|
||||
}
|
||||
|
||||
public void setStn_useQtfFilterDirectionAsAntennaHeading(
|
||||
boolean stn_useQtfFilterDirectionAsAntennaHeading) {
|
||||
this.stn_qtfFilterDirectionAsAntennaHeadingOptedOut =
|
||||
!stn_useQtfFilterDirectionAsAntennaHeading;
|
||||
}
|
||||
|
||||
public boolean isQtfFilterDirectionAsAntennaHeadingEnabled() {
|
||||
return !stn_pstRotatorEnabled && !stn_qtfFilterDirectionAsAntennaHeadingOptedOut;
|
||||
}
|
||||
|
||||
public String getStn_pstRotatorHost() {
|
||||
return stn_pstRotatorHost;
|
||||
}
|
||||
@@ -1224,7 +1242,9 @@ public class ChatPreferences {
|
||||
}
|
||||
|
||||
public void setStn_qtfDefault(double stn_qtfDefault) {
|
||||
this.stn_qtfDefault = stn_qtfDefault;
|
||||
if (Double.isFinite(stn_qtfDefault) && stn_qtfDefault >= 0.0 && stn_qtfDefault <= 360.0) {
|
||||
this.stn_qtfDefault = stn_qtfDefault;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStn_loginAFKState() {
|
||||
@@ -1742,6 +1762,12 @@ public class ChatPreferences {
|
||||
stn_qtfDefault.setTextContent(this.stn_qtfDefault+"");
|
||||
station.appendChild(stn_qtfDefault);
|
||||
|
||||
Element stn_qtfFilterDirectionAsAntennaHeadingOptedOut =
|
||||
doc.createElement("stn_qtfFilterDirectionAsAntennaHeadingOptedOut");
|
||||
stn_qtfFilterDirectionAsAntennaHeadingOptedOut.setTextContent(
|
||||
Boolean.toString(this.stn_qtfFilterDirectionAsAntennaHeadingOptedOut));
|
||||
station.appendChild(stn_qtfFilterDirectionAsAntennaHeadingOptedOut);
|
||||
|
||||
Element stn_pathAnalysisOwnAntennaHeightMeters = doc.createElement("stn_pathAnalysisOwnAntennaHeightMeters");
|
||||
stn_pathAnalysisOwnAntennaHeightMeters.setTextContent(this.stn_pathAnalysisOwnAntennaHeightMeters + "");
|
||||
station.appendChild(stn_pathAnalysisOwnAntennaHeightMeters);
|
||||
@@ -2628,7 +2654,7 @@ public class ChatPreferences {
|
||||
|
||||
stn_antennaBeamWidthDeg = getDouble(stationEl, stn_antennaBeamWidthDeg, "stn_antennaBeamWidthDeg");
|
||||
stn_maxQRBDefault = getDouble(stationEl, stn_maxQRBDefault, "stn_maxQRBDefault");
|
||||
stn_qtfDefault = getDouble(stationEl, stn_qtfDefault, "stn_qtfDefault");
|
||||
setStn_qtfDefault(getDouble(stationEl, stn_qtfDefault, "stn_qtfDefault"));
|
||||
|
||||
stn_pathAnalysisOwnAntennaHeightMeters = getDouble(
|
||||
stationEl,
|
||||
@@ -2738,6 +2764,16 @@ public class ChatPreferences {
|
||||
stn_bandActive10G = getBoolean(stationEl, stn_bandActive10G, "stn_bandActive10G");
|
||||
|
||||
stn_pstRotatorEnabled = getBoolean(stationEl, stn_pstRotatorEnabled, "stn_pstRotatorEnabled");
|
||||
/*
|
||||
* The positive version-8 setting cannot distinguish an explicit false from its old
|
||||
* default. Absence of the opt-out marker therefore performs the approved migration
|
||||
* to enabled; once written, the marker preserves every later user decision.
|
||||
*/
|
||||
stn_qtfFilterDirectionAsAntennaHeadingOptedOut = getBoolean(
|
||||
stationEl,
|
||||
false,
|
||||
"stn_qtfFilterDirectionAsAntennaHeadingOptedOut"
|
||||
);
|
||||
|
||||
setStn_pstRotatorHost(
|
||||
getText(
|
||||
@@ -3411,10 +3447,18 @@ public class ChatPreferences {
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
|
||||
initializeActualQtfFromDefaultIfRequired();
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private void initializeActualQtfFromDefaultIfRequired() {
|
||||
if (!actualQtfInitializedFromPreferences) {
|
||||
actualQTF.set(stn_qtfDefault);
|
||||
actualQtfInitializedFromPreferences = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return double array with separated values for each doublevalue, seperated by ";", empty array if none
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<praktiKST>
|
||||
<configVersion>8</configVersion>
|
||||
<configVersion>9</configVersion>
|
||||
<station>
|
||||
<LoginCallSign>DO5AMF</LoginCallSign>
|
||||
<LoginPassword>changeMe</LoginPassword>
|
||||
@@ -13,6 +13,7 @@
|
||||
<stn_antennaBeamWidthDeg>60.0</stn_antennaBeamWidthDeg>
|
||||
<stn_maxQRBDefault>1333.0</stn_maxQRBDefault>
|
||||
<stn_qtfDefault>135.0</stn_qtfDefault>
|
||||
<stn_qtfFilterDirectionAsAntennaHeadingOptedOut>false</stn_qtfFilterDirectionAsAntennaHeadingOptedOut>
|
||||
<stn_bandActive144>true</stn_bandActive144>
|
||||
<stn_bandActive432>true</stn_bandActive432>
|
||||
<stn_bandActive1240>true</stn_bandActive1240>
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package kst4contest.model;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Files;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ChatPreferencesQtfHeadingTest {
|
||||
|
||||
@TempDir
|
||||
Path directory;
|
||||
|
||||
@Test
|
||||
void loadedQtfDefaultInitializesTheRuntimeHeadingOnce() {
|
||||
Path file = directory.resolve("preferences.xml");
|
||||
ChatPreferences written = new ChatPreferences(file);
|
||||
written.setStn_qtfDefault(247.5);
|
||||
assertTrue(written.writePreferencesToXmlFile());
|
||||
|
||||
ChatPreferences restored = new ChatPreferences(file);
|
||||
assertTrue(restored.readPreferencesFromXmlFile());
|
||||
assertEquals(247.5, restored.getActualQTF().get(), 0.0001);
|
||||
|
||||
restored.getActualQTF().set(91.0);
|
||||
assertTrue(restored.readPreferencesFromXmlFile());
|
||||
assertEquals(91.0, restored.getActualQTF().get(), 0.0001,
|
||||
"reloading preferences overwrote a later runtime rotor heading");
|
||||
}
|
||||
|
||||
@Test
|
||||
void eachProfileStartsWithItsOwnQtfDefault() {
|
||||
Path rootFile = directory.resolve("preferences.xml");
|
||||
Path operatorFile = directory.resolve("profiles/OP2/preferences.xml");
|
||||
writeQtfDefault(rootFile, 135.0);
|
||||
writeQtfDefault(operatorFile, 302.0);
|
||||
|
||||
ChatPreferences root = read(rootFile);
|
||||
ChatPreferences operator = read(operatorFile);
|
||||
|
||||
assertEquals(135.0, root.getActualQTF().get(), 0.0001);
|
||||
assertEquals(302.0, operator.getActualQTF().get(), 0.0001);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qtfFilterHeadingSynchronizationDefaultsToEnabledForRootAndOperatorProfiles() {
|
||||
ChatPreferences root = new ChatPreferences(directory.resolve("preferences.xml"));
|
||||
ChatPreferences operator = new ChatPreferences(directory.resolve("profiles/OP2/preferences.xml"));
|
||||
|
||||
assertTrue(root.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(root.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
assertTrue(operator.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(operator.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void primaryRotatorOnlySuspendsTheStoredManualHeadingPreference() {
|
||||
ChatPreferences preferences = new ChatPreferences(directory.resolve("preferences.xml"));
|
||||
|
||||
preferences.setStn_pstRotatorEnabled(true);
|
||||
|
||||
assertTrue(preferences.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertFalse(preferences.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
|
||||
preferences.setStn_pstRotatorEnabled(false);
|
||||
|
||||
assertTrue(preferences.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(preferences.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionEightProfilesMigrateToTheEnabledDefault() throws IOException {
|
||||
Path file = directory.resolve("preferences.xml");
|
||||
Files.writeString(file, """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<praktiKST>
|
||||
<configVersion>8</configVersion>
|
||||
<station>
|
||||
<stn_pstRotatorEnabled>false</stn_pstRotatorEnabled>
|
||||
<stn_useQtfFilterDirectionAsAntennaHeading>false</stn_useQtfFilterDirectionAsAntennaHeading>
|
||||
</station>
|
||||
</praktiKST>
|
||||
""");
|
||||
|
||||
ChatPreferences migrated = read(file);
|
||||
|
||||
assertTrue(migrated.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(migrated.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
|
||||
assertTrue(migrated.writePreferencesToXmlFile());
|
||||
String migratedXml = Files.readString(file);
|
||||
assertTrue(migratedXml.contains("<configVersion>9</configVersion>"));
|
||||
assertTrue(migratedXml.contains(
|
||||
"<stn_qtfFilterDirectionAsAntennaHeadingOptedOut>false"));
|
||||
assertTrue(read(file).isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitOptOutSurvivesRestartAndPrimaryRotatorChanges() throws IOException {
|
||||
Path file = directory.resolve("preferences.xml");
|
||||
ChatPreferences preferences = new ChatPreferences(file);
|
||||
preferences.setStn_useQtfFilterDirectionAsAntennaHeading(false);
|
||||
assertTrue(preferences.writePreferencesToXmlFile());
|
||||
|
||||
ChatPreferences restored = read(file);
|
||||
assertFalse(restored.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertFalse(restored.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
assertTrue(Files.readString(file).contains(
|
||||
"<stn_qtfFilterDirectionAsAntennaHeadingOptedOut>true"));
|
||||
|
||||
restored.setStn_pstRotatorEnabled(true);
|
||||
restored.setStn_pstRotatorEnabled(false);
|
||||
|
||||
assertFalse(restored.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertFalse(restored.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void profilesKeepIndependentManualHeadingDecisions() {
|
||||
Path rootFile = directory.resolve("preferences.xml");
|
||||
Path operatorFile = directory.resolve("profiles/OP2/preferences.xml");
|
||||
ChatPreferences root = new ChatPreferences(rootFile);
|
||||
root.setStn_useQtfFilterDirectionAsAntennaHeading(false);
|
||||
assertTrue(root.writePreferencesToXmlFile());
|
||||
|
||||
ChatPreferences operator = new ChatPreferences(operatorFile);
|
||||
assertTrue(operator.writePreferencesToXmlFile());
|
||||
|
||||
assertFalse(read(rootFile).isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(read(operatorFile).isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
}
|
||||
|
||||
@Test
|
||||
void laterRotorHeadingReplacesTheLoadedStartValue() {
|
||||
Path file = directory.resolve("preferences.xml");
|
||||
ChatPreferences written = new ChatPreferences(file);
|
||||
written.setStn_qtfDefault(188.0);
|
||||
written.setStn_pstRotatorEnabled(true);
|
||||
assertTrue(written.writePreferencesToXmlFile());
|
||||
|
||||
ChatPreferences restored = read(file);
|
||||
restored.getActualQTF().set(271.25);
|
||||
|
||||
assertEquals(271.25, restored.getActualQTF().get(), 0.0001);
|
||||
assertEquals(188.0, restored.getStn_qtfDefault(), 0.0001,
|
||||
"a runtime heading changed the persisted start direction");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"NaN", "Infinity", "-0.1", "360.1"})
|
||||
void invalidStoredQtfDefaultsAreIgnored(String invalidValue) throws IOException {
|
||||
Path file = directory.resolve("preferences.xml");
|
||||
ChatPreferences written = new ChatPreferences(file);
|
||||
written.setStn_qtfDefault(247.5);
|
||||
assertTrue(written.writePreferencesToXmlFile());
|
||||
Files.writeString(file, Files.readString(file).replace(
|
||||
"<stn_qtfDefault>247.5</stn_qtfDefault>",
|
||||
"<stn_qtfDefault>" + invalidValue + "</stn_qtfDefault>"));
|
||||
|
||||
ChatPreferences restored = read(file);
|
||||
|
||||
assertEquals(135.0, restored.getStn_qtfDefault(), 0.0001);
|
||||
assertEquals(135.0, restored.getActualQTF().get(), 0.0001);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"NaN", "Infinity", "-Infinity", "-0.1", "360.1"})
|
||||
void invalidQtfDefaultsCannotReplaceAValidValue(String invalidValue) {
|
||||
ChatPreferences preferences = new ChatPreferences(directory.resolve("preferences.xml"));
|
||||
preferences.setStn_qtfDefault(247.5);
|
||||
|
||||
preferences.setStn_qtfDefault(Double.parseDouble(invalidValue));
|
||||
|
||||
assertEquals(247.5, preferences.getStn_qtfDefault(), 0.0001);
|
||||
}
|
||||
|
||||
private void writeQtfDefault(Path file, double qtf) {
|
||||
ChatPreferences preferences = new ChatPreferences(file);
|
||||
preferences.setStn_qtfDefault(qtf);
|
||||
assertTrue(preferences.writePreferencesToXmlFile());
|
||||
}
|
||||
|
||||
private ChatPreferences read(Path file) {
|
||||
ChatPreferences preferences = new ChatPreferences(file);
|
||||
assertTrue(preferences.readPreferencesFromXmlFile());
|
||||
return preferences;
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,8 @@ class ChatPreferencesRoundTripTest {
|
||||
assertTrue(fromTemplate.getLst_txtShortCutBtnList().snapshot().contains("sked"));
|
||||
assertTrue(fromTemplate.isStn_bandActive144());
|
||||
assertFalse(fromTemplate.isStn_bandActive10G());
|
||||
assertTrue(fromTemplate.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(fromTemplate.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -211,6 +213,8 @@ class ChatPreferencesRoundTripTest {
|
||||
assertEquals("JO31AA", legacy.getStn_loginLocatorMainCat());
|
||||
assertEquals(3, legacy.getLoginChatCategoryMain().getCategoryNumber());
|
||||
assertEquals(8123, legacy.getNotify_dxclusterServerPort());
|
||||
assertTrue(legacy.isStn_useQtfFilterDirectionAsAntennaHeading());
|
||||
assertTrue(legacy.isQtfFilterDirectionAsAntennaHeadingEnabled());
|
||||
// A tag that is missing keeps the compiled-in default.
|
||||
assertEquals(new ChatPreferences(legacyFile).getStn_on4kstServersPort(),
|
||||
legacy.getStn_on4kstServersPort());
|
||||
|
||||
@@ -54,7 +54,7 @@ class ChatPreferencesDebugModeTest {
|
||||
assertTrue(written.writePreferencesToXmlFile());
|
||||
|
||||
String writtenXml = Files.readString(preferencesFile);
|
||||
assertTrue(writtenXml.contains("<configVersion>8</configVersion>"));
|
||||
assertTrue(writtenXml.contains("<configVersion>9</configVersion>"));
|
||||
assertTrue(writtenXml.contains(
|
||||
"<messageHandling_debugModeToFileEnabled>false"
|
||||
+ "</messageHandling_debugModeToFileEnabled>"));
|
||||
|
||||
@@ -117,7 +117,7 @@ class ChatPreferencesLayoutPersistenceTest {
|
||||
assertFalse(writtenXml.contains("UNSAVED-CALL"));
|
||||
assertTrue(writtenXml.contains("<futureExtension mode=\"keep-me\">"));
|
||||
assertTrue(writtenXml.contains("<futureLayoutValue>untouched</futureLayoutValue>"));
|
||||
assertTrue(writtenXml.contains("<configVersion>8</configVersion>"));
|
||||
assertTrue(writtenXml.contains("<configVersion>9</configVersion>"));
|
||||
assertTrue(writtenXml.contains("<GUIscn_ChatwindowMainSceneSizeHW>812.0;1340.0"));
|
||||
assertTrue(writtenXml.contains("<GUIstationMapClusteringEnabled>false"
|
||||
+ "</GUIstationMapClusteringEnabled>"));
|
||||
|
||||
@@ -32,7 +32,7 @@ class ChatPreferencesStationMapClusteringTest {
|
||||
assertTrue(written.writePreferencesToXmlFile());
|
||||
|
||||
String writtenXml = Files.readString(preferencesFile);
|
||||
assertTrue(writtenXml.contains("<configVersion>8</configVersion>"));
|
||||
assertTrue(writtenXml.contains("<configVersion>9</configVersion>"));
|
||||
assertTrue(writtenXml.contains("<GUIstationMapClusteringEnabled>false"
|
||||
+ "</GUIstationMapClusteringEnabled>"));
|
||||
|
||||
|
||||
Reference in new issue
Block a user