From 8bea4111f09701abe102dc624534c80b171fac2e Mon Sep 17 00:00:00 2001 From: Marc Froehlich Date: Sat, 30 Mar 2024 00:50:16 +0100 Subject: [PATCH] - introduced qrv tags for callsigns, only UI so far --- .../controller/ChatController.java | 7 ++ .../kst4contest/controller/DBController.java | 2 +- .../MessageBusManagementThread.java | 14 +++ .../locatorUtils/DirectionUtils.java | 27 ++++- .../java/kst4contest/model/ChatMember.java | 97 ++++++++++++++- .../kst4contest/utils/PlayAudioUtils.java | 6 + .../view/Kst4ContestApplication.java | 113 +++++++++++++++++- src/main/resources/praktiKSTpreferences.xml | 6 +- src/main/resources/tick.mp3 | Bin 0 -> 3168 bytes 9 files changed, 254 insertions(+), 18 deletions(-) create mode 100644 src/main/resources/tick.mp3 diff --git a/src/main/java/kst4contest/controller/ChatController.java b/src/main/java/kst4contest/controller/ChatController.java index 3f8ada7..ea2cbaf 100644 --- a/src/main/java/kst4contest/controller/ChatController.java +++ b/src/main/java/kst4contest/controller/ChatController.java @@ -1170,6 +1170,13 @@ category = new ChatCategory(2); chatMember -> chatMember.resetWorkedInformationAtAllBands()); } + + public void resetQRVInfoInGuiLists() { + + this.chatController.getLst_chatMemberList().forEach( + chatMember -> chatMember.resetQRVInformationAtAllBands()); + + } /** * Setting the initial parameters at the chat diff --git a/src/main/java/kst4contest/controller/DBController.java b/src/main/java/kst4contest/controller/DBController.java index 2e895dd..e55e3db 100644 --- a/src/main/java/kst4contest/controller/DBController.java +++ b/src/main/java/kst4contest/controller/DBController.java @@ -220,7 +220,7 @@ public class DBController { } catch (SQLException e) { System.err.println("[DBH, ERROR:] Chatmember could not been stored."); e.printStackTrace(); - connection.close(); +// connection.close(); //Todo commented out due to errors } } diff --git a/src/main/java/kst4contest/controller/MessageBusManagementThread.java b/src/main/java/kst4contest/controller/MessageBusManagementThread.java index 5ad4626..4018be2 100644 --- a/src/main/java/kst4contest/controller/MessageBusManagementThread.java +++ b/src/main/java/kst4contest/controller/MessageBusManagementThread.java @@ -625,8 +625,22 @@ public class MessageBusManagementThread extends Thread { newMessage.getReceiver().getQra(), client.getChatPreferences().getStn_maxQRBDefault(), client.getChatPreferences().getStn_antennaBeamWidthDeg())) { + + if (this.client.getChatPreferences().isNotify_playSimpleSounds()) { + //play only tick sound if the sender was not set directedtome before + if (!newMessage.getSender().isInAngleAndRange()) { + this.client.getPlayAudioUtils().playNoiseLauncher('-'); + } + } newMessage.getSender().setInAngleAndRange(true); + System.out.println(">>>>>>>>>> Anglewarning <<<<<<<<<< " + newMessage.getSender().getCallSign() + ", " + newMessage.getSender().getQra() + " -> " + newMessage.getReceiver().getCallSign() + ", " + newMessage.getReceiver().getQra() + " = " + + new Location(newMessage.getSender().getQra()).getBearing(new Location(newMessage.getReceiver().getQra())) + + " / sender bearing to me: " + new Location(newMessage.getSender().getQra()).getBearing(new Location(client.getChatPreferences().getLoginLocator()))); + } else { + System.out.println("-notinangle- " + newMessage.getSender().getCallSign() + ", " + newMessage.getSender().getQra() + " -> " + newMessage.getReceiver().getCallSign() + ", " + newMessage.getReceiver().getQra() + " = " + + new Location(newMessage.getSender().getQra()).getBearing(new Location(newMessage.getReceiver().getQra())) + + " ; sender bearing to me: " + new Location(newMessage.getSender().getQra()).getBearing(new Location(client.getChatPreferences().getLoginLocator()))); newMessage.getSender().setInAngleAndRange(false); } diff --git a/src/main/java/kst4contest/locatorUtils/DirectionUtils.java b/src/main/java/kst4contest/locatorUtils/DirectionUtils.java index 4c86db7..bdb1fc6 100644 --- a/src/main/java/kst4contest/locatorUtils/DirectionUtils.java +++ b/src/main/java/kst4contest/locatorUtils/DirectionUtils.java @@ -29,17 +29,34 @@ public class DirectionUtils { //check bearing of sender to receiver double bearingOfSekdSenderToSkedReceiver = skedSenderLocation.getBearing(skedReceiverLocation); - System.out.println("skedTX -> skedTX deg: " + bearingOfSekdSenderToSkedReceiver); +// System.out.println("skedTX -> skedTX deg: " + bearingOfSekdSenderToSkedReceiver); double bearingOfSekdSenderToMe = skedSenderLocation.getBearing(myLocation); - System.out.println("skedTX -> me deg: " + bearingOfSekdSenderToMe); +// System.out.println("skedTX -> me deg: " + bearingOfSekdSenderToMe); - if (DirectionUtils.isAngleInRange(bearingOfSekdSenderToMe,bearingOfSekdSenderToSkedReceiver, hisAntennaBeamWidth)) { + /** + * simple mech works + */ +// if (bearingOfSekdSenderToMe >= bearingOfSekdSenderToSkedReceiver) { +// if (bearingOfSekdSenderToMe-bearingOfSekdSenderToSkedReceiver <= hisAntennaBeamWidth/2){ +// System.out.println(bearingOfSekdSenderToMe-bearingOfSekdSenderToSkedReceiver + " <= " + hisAntennaBeamWidth); +// return true; +// } +// } else if ((bearingOfSekdSenderToMe <= bearingOfSekdSenderToSkedReceiver)) { +// if (bearingOfSekdSenderToSkedReceiver-bearingOfSekdSenderToMe <= hisAntennaBeamWidth/2){ +// return true; +// } +// } else return false; + /** + * simple mech end + */ + + if (DirectionUtils.isAngleInRange(bearingOfSekdSenderToSkedReceiver, bearingOfSekdSenderToMe, hisAntennaBeamWidth)) { //I may should get "/2" because of 50% of the 3dB opening angle if txer is directed to sender exactly - System.out.println("isinangleandrange!"); +// System.out.println("------------> isinangleandrange!"); return true; } else { - System.out.println("not in angle and reach"); +// System.out.println("not in angle and reach"); return false; } } diff --git a/src/main/java/kst4contest/model/ChatMember.java b/src/main/java/kst4contest/model/ChatMember.java index 22b63be..bff41d1 100644 --- a/src/main/java/kst4contest/model/ChatMember.java +++ b/src/main/java/kst4contest/model/ChatMember.java @@ -40,6 +40,17 @@ public class ChatMember { boolean worked5600; boolean worked10G; + boolean qrv144; + boolean qrv432; + boolean qrv1240; + boolean qrv2300; + boolean qrv3400; + boolean qrv5600; + boolean qrv10G; + boolean qrvAny; + + + public boolean isInAngleAndRange() { return isInAngleAndRange; } @@ -128,6 +139,70 @@ public class ChatMember { worked10G = worked10g; } + public boolean isQrv144() { + return qrv144; + } + + public void setQrv144(boolean qrv144) { + this.qrv144 = qrv144; + } + + public boolean isQrv432() { + return qrv432; + } + + public void setQrv432(boolean qrv432) { + this.qrv432 = qrv432; + } + + public boolean isQrv1240() { + return qrv1240; + } + + public void setQrv1240(boolean qrv1240) { + this.qrv1240 = qrv1240; + } + + public boolean isQrv2300() { + return qrv2300; + } + + public void setQrv2300(boolean qrv2300) { + this.qrv2300 = qrv2300; + } + + public boolean isQrv3400() { + return qrv3400; + } + + public void setQrv3400(boolean qrv3400) { + this.qrv3400 = qrv3400; + } + + public boolean isQrv5600() { + return qrv5600; + } + + public void setQrv5600(boolean qrv5600) { + this.qrv5600 = qrv5600; + } + + public boolean isQrv10G() { + return qrv10G; + } + + public void setQrv10G(boolean qrv10G) { + this.qrv10G = qrv10G; + } + + public boolean isQrvAny() { + return qrvAny; + } + + public void setQrvAny(boolean qrvAny) { + this.qrvAny = qrvAny; + } + public int[] getWorkedCategories() { return workedCategories; } @@ -180,12 +255,6 @@ public class ChatMember { QTFdirection = qTFdirection; } -// public int getWorkedCategory() { -// return workedCategory; -// } -// public void setWorkedCategory(int workedCategory) { -// this.workedCategory = workedCategory; -// } public String getCallSign() { return callSign; } @@ -253,6 +322,22 @@ public class ChatMember { this.setWorked10G(false); } + /** + * Sets all worked information of this object to false. Scope: GUI, Reset Button + * for worked info, called by appcontroller + */ + public void resetQRVInformationAtAllBands() { + + this.setQrvAny(true); + this.setQrv144(true); + this.setQrv432(true); + this.setQrv1240(true); + this.setQrv2300(true); + this.setQrv3400(true); + this.setQrv5600(true); + this.setQrv10G(true); + } + @Override public String toString() { String chatMemberSerialization = ""; diff --git a/src/main/java/kst4contest/utils/PlayAudioUtils.java b/src/main/java/kst4contest/utils/PlayAudioUtils.java index 3d17294..51bd9e0 100644 --- a/src/main/java/kst4contest/utils/PlayAudioUtils.java +++ b/src/main/java/kst4contest/utils/PlayAudioUtils.java @@ -20,11 +20,13 @@ public class PlayAudioUtils { */ public PlayAudioUtils() { + ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/NOISESTARTUP.mp3", "NOISESTARTUP.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/NOISECQWINDOW.mp3", "NOISECQWINDOW.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/NOISEPMWINDOW.mp3", "NOISEPMWINDOW.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/NOISEERROR.mp3", "NOISEERROR.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/NOISENOTIFY.mp3", "NOISENOTIFY.mp3"); + ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/tick.mp3", "tick.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/LTTRA.mp3", "LTTRA.mp3"); ApplicationFileUtils.copyResourceIfRequired(ApplicationConstants.APPLICATION_NAME, "/LTTRB.mp3", "LTTRB.mp3"); @@ -119,6 +121,7 @@ public class PlayAudioUtils { *
* * case '!': Startup
+ * case '-': tick
* case 'C': CQ Window new entry
* case 'P': PM Window new entry
* case 'E': Error occured
@@ -134,6 +137,9 @@ public class PlayAudioUtils { switch (actionChar){ + case '-': + musicList.add(new Media(new File (ApplicationFileUtils.getFilePath(ApplicationConstants.APPLICATION_NAME, "/tick.mp3")).toURI().toString())); + break; case '!': musicList.add(new Media(new File (ApplicationFileUtils.getFilePath(ApplicationConstants.APPLICATION_NAME, "/NOISESTARTUP.mp3")).toURI().toString())); break; diff --git a/src/main/java/kst4contest/view/Kst4ContestApplication.java b/src/main/java/kst4contest/view/Kst4ContestApplication.java index 573a41e..e1b6bd7 100644 --- a/src/main/java/kst4contest/view/Kst4ContestApplication.java +++ b/src/main/java/kst4contest/view/Kst4ContestApplication.java @@ -4,6 +4,8 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; import javafx.beans.binding.Bindings; @@ -97,6 +99,36 @@ public class Kst4ContestApplication extends Application { selectedCallSignDownerSiteGridPane.add(new Label("Last activity: " + new Utils4KST().time_convertEpochToReadable(selectedCallSignInfoStageChatMember.getActivityTimeLastInEpoch()+"")), 0,2,1,1); selectedCallSignDownerSiteGridPane.add(new Label(("(" + Utils4KST.time_getSecondsBetweenEpochAndNow(selectedCallSignInfoStageChatMember.getActivityTimeLastInEpoch()+"") /60%60) +" min ago)"), 0,3,1,1); + /** + * users qrv info setting will follow here + */ + CheckBox chkbx_tagMemberNotQRVFurtherInfoPane = new CheckBox("tag NOT QRV ALL"); + chkbx_tagMemberNotQRVFurtherInfoPane.setSelected(selectedCallSignInfoStageChatMember.isQrvAny()); + chkbx_tagMemberNotQRVFurtherInfoPane.selectedProperty().addListener(new ChangeListener() { + @Override + public void changed(ObservableValue observable, Boolean oldValue, Boolean newValue) { + //if selected: NOT QRV; if NOT selected: is qrv! + if (!newValue) { + chkbx_tagMemberNotQRVFurtherInfoPane.selectedProperty().setValue(true); + } else { + chkbx_tagMemberNotQRVFurtherInfoPane.selectedProperty().setValue(false); + } + } + }); + + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 144"), 2,0,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 432"), 2,1,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 23cm"), 2,2,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 13cm"), 2,3,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 9cm"), 3,0,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 6cm"), 3,1,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv 3cm"), 3,2,1,1); + selectedCallSignDownerSiteGridPane.add(new CheckBox("tag not qrv all"), 3,3,1,1); + + /** + * users qrv info setting ending + */ + Button selectedCallSignShowAsPathBtn = new Button("Show path in AS"); selectedCallSignShowAsPathBtn.setOnAction(new EventHandler() { @Override @@ -105,7 +137,7 @@ public class Kst4ContestApplication extends Application { } }); - selectedCallSignDownerSiteGridPane.add(selectedCallSignShowAsPathBtn, 1,0,1,3); + selectedCallSignDownerSiteGridPane.add(selectedCallSignShowAsPathBtn, 1,0,1,2); @@ -699,6 +731,10 @@ public class Kst4ContestApplication extends Application { shf3_subcol.prefWidthProperty().bind(tbl_chatMemberTable.widthProperty().divide(32)); +// TableColumn unworkableCol = new TableColumn("qrv"); +// unworkableCol.setCellFactory(CheckBoxTableCell.forTableColumn("lalala", c -> +// System.out.println(c.getCallSign()))); + // TableColumn uhfCol_subcol = new TableColumn("432"); //TODO: Worked band analysis // TableColumn shf23_subcol = new TableColumn("23"); // TableColumn shf13_subcol = new TableColumn("13"); @@ -4299,7 +4335,7 @@ public class Kst4ContestApplication extends Application { } System.out.println("[Main.java, Info]: Setted the QRB: " + txtFldstn_maxQRBDefault.getText()); - chatcontroller.getChatPreferences().setStn_antennaBeamWidthDeg(Double.parseDouble(txtFldstn_maxQRBDefault.getText())); + chatcontroller.getChatPreferences().setStn_maxQRBDefault(Double.parseDouble(txtFldstn_maxQRBDefault.getText())); } }); @@ -4709,7 +4745,7 @@ public class Kst4ContestApplication extends Application { // "Switch bands, prefix worked by others alert, direction notifications, notification pattern matchers"); // CheckBox chkBxEnableTRXMsgbyUCX = new CheckBox(); - Label lblNotifyEnableSimpleSounds = new Label("Enable audio notifications at: startup, new personal messages, other"); + Label lblNotifyEnableSimpleSounds = new Label("Enable simple audio notifications at: new personal message, new sked in ur dir, other"); Label lblNotifyEnableCWSounds = new Label("Enable CW callsign spelling for new personal messages"); Label lblNotifyEnableVoiceSounds = new Label("Enable phonetic callsign spelling for new personal messages"); @@ -5119,6 +5155,9 @@ public class Kst4ContestApplication extends Application { btnOptionspnlConnect.setDisable(false); btnOptionspnlDisconnect.setDisable(false); btnOptionspnlDisconnectOnly.setDisable(true); + txtFldstn_antennaBeamWidthDeg.setDisable(false); + txtFldstn_qtfDefault.setDisable(false); + txtFldstn_maxQRBDefault.setDisable(false); menuItemOptionsSetFrequencyAsName.setDisable(true); menuItemOptionsAwayBack.setDisable(true); } @@ -5405,3 +5444,71 @@ public class Kst4ContestApplication extends Application { // } } + +/** + * This cell type is used to declare buttons which can be placed in the tableview + * + * // source: https://stackoverflow.com/questions/76248808/how-do-i-add-a-button-into-a-jfx-tableview + */ +class ActionButtonTableCell extends TableCell { + private final ToggleButton actionButton; + + public ActionButtonTableCell(String label, Consumer function) { + this.getStyleClass().add("action-button-table-cell"); + this.actionButton = new ToggleButton(label); + this.actionButton.setOnAction(e -> function.accept(getCurrentItem())); + this.actionButton.setMaxWidth(Double.MAX_VALUE); + } + public S getCurrentItem() { + // No need for a cast here: + System.out.println("<<<<<<<<<<<<<<<<<<< Callback, TableCell> forTableColumn(String label, Consumer function) { + return param -> new ActionButtonTableCell<>(label, function); + } + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setGraphic(null); + } else { + setGraphic(actionButton); + } + } +} + +/** + * This cell type is used to declare buttons which can be placed in the tableview + * + * // source: https://stackoverflow.com/questions/76248808/how-do-i-add-a-button-into-a-jfx-tableview + */ +class CheckBoxTableCell extends TableCell { + private final CheckBox actionCheckBox; + + public CheckBoxTableCell(String label, Consumer function) { + this.getStyleClass().add("action-button-table-cell"); + this.actionCheckBox = new CheckBox(label); + this.actionCheckBox.setOnAction(e -> function.accept(getCurrentItem())); +// this.actionCheckBox.setMaxWidth(Double.MAX_VALUE); + } + public S getCurrentItem() { + // No need for a cast here: + System.out.println("<<<<<<<<<<<<<<<<<<< Callback, TableCell> forTableColumn(String label, Consumer function) { + return param -> new CheckBoxTableCell<>(label, function); + } + @Override + public void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setGraphic(null); + } else { + setGraphic(actionCheckBox); + } + } +} \ No newline at end of file diff --git a/src/main/resources/praktiKSTpreferences.xml b/src/main/resources/praktiKSTpreferences.xml index 3619536..9df4046 100644 --- a/src/main/resources/praktiKSTpreferences.xml +++ b/src/main/resources/praktiKSTpreferences.xml @@ -1,10 +1,10 @@ - DO5SA + DO5AMF kst4contest.test - Paule - JO51DI + Marc + JN49GL 2 50 900 diff --git a/src/main/resources/tick.mp3 b/src/main/resources/tick.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..57c175aff6aa834e668d1cefc88324b224501c6b GIT binary patch literal 3168 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 Z{0B&pDOw%^fCzwv2msWduKat(-vPIa*#H0l literal 0 HcmV?d00001