mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 05:42:58 +02:00
Use the macOS system menu bar in all windows
On macOS the main menus move into the system menu bar and are shared with the secondary windows, so they stay available whichever window has the focus. The connection state indicator becomes a read-only LINK menu there instead of the status bar button. Other platforms are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2b52bd81c0
commit
9111b75b42
5 files changed
+165
-2
No files matched your search
@@ -0,0 +1,29 @@
|
||||
package kst4contest.utils;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Small helpers for operating-system specific UI behaviour.
|
||||
*/
|
||||
public final class PlatformUtils {
|
||||
|
||||
private PlatformUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the running JVM reports macOS as operating system
|
||||
*/
|
||||
public static boolean isMacOs() {
|
||||
return isMacOs(System.getProperty("os.name"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an {@code os.name} value for macOS. A missing value is treated as "not macOS".
|
||||
*/
|
||||
public static boolean isMacOs(String osName) {
|
||||
if (osName == null) {
|
||||
return false;
|
||||
}
|
||||
return osName.toLowerCase(Locale.ROOT).startsWith("mac");
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ import javafx.stage.Screen;
|
||||
import kst4contest.logic.BandOpportunityResolver;
|
||||
import kst4contest.utils.ApplicationFileLogging;
|
||||
import kst4contest.utils.ApplicationFileUtils;
|
||||
import kst4contest.utils.PlatformUtils;
|
||||
import kst4contest.view.map.StationMapBridge;
|
||||
import kst4contest.controller.ActiveOperatorProfile;
|
||||
import kst4contest.controller.OperatorProfileStore;
|
||||
@@ -133,6 +134,11 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
private On4KstConnectionState lastDisplayedConnectionState;
|
||||
private String lastDisplayedConnectionDetail = "";
|
||||
|
||||
/** Main window menu bar; its menus are shared with the secondary windows on macOS. */
|
||||
private MenuBar mainScreenMenuBar;
|
||||
/** macOS only: connection state shown as menu title in the system menu bar. */
|
||||
private final Menu menuConnectionStateMacOs = new Menu("LINK");
|
||||
private final MenuItem menuItemConnectionStateDetailMacOs = new MenuItem();
|
||||
/** Scenes whose stylesheet follows the light/dark design switch. */
|
||||
private final List<Scene> themedScenes = new ArrayList<>();
|
||||
|
||||
@@ -201,6 +207,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
chatcontroller.getChatPreferences(),
|
||||
this::requestLayoutSave
|
||||
);
|
||||
installSharedSystemMenuBar(stationMapView.getScene());
|
||||
stationMapBridge = new StationMapBridge(
|
||||
chatcontroller,
|
||||
tbl_chatMember,
|
||||
@@ -4595,6 +4602,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
Scene candidatesScene = new Scene(root, 360, 500);
|
||||
registerThemedScene(candidatesScene);
|
||||
stage.setOnHidden(event -> themedScenes.remove(candidatesScene));
|
||||
installSharedSystemMenuBar(candidatesScene);
|
||||
stage.setScene(candidatesScene);
|
||||
stage.show();
|
||||
}
|
||||
@@ -5756,9 +5764,46 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
MenuBar menubar = new MenuBar();
|
||||
menubar.getMenus().addAll(fileMenu, optionsMenu, windowMenu, helpMenu); // macromenu deleted
|
||||
|
||||
// On macOS the menu belongs into the system menu bar at the top of the screen.
|
||||
// The node stays in the status bar pane but is not rendered there.
|
||||
// The connection state indicator moves into the menu bar as a read-only menu.
|
||||
if (PlatformUtils.isMacOs()) {
|
||||
menuItemConnectionStateDetailMacOs.setDisable(true);
|
||||
menuConnectionStateMacOs.getItems().setAll(menuItemConnectionStateDetailMacOs);
|
||||
menubar.getMenus().add(menuConnectionStateMacOs);
|
||||
menubar.setUseSystemMenuBar(true);
|
||||
}
|
||||
|
||||
return menubar;
|
||||
}
|
||||
|
||||
/**
|
||||
* On macOS, attaches an invisible system menu bar to a secondary window, so the
|
||||
* main menus stay visible in the screen menu bar while that window has the focus.
|
||||
* The menus are shared with the main window instead of copied, so enabled state and
|
||||
* labels stay consistent everywhere. Does nothing on other platforms.
|
||||
*/
|
||||
private void installSharedSystemMenuBar(Scene scene) {
|
||||
if (!PlatformUtils.isMacOs() || mainScreenMenuBar == null || scene == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Pane rootPane;
|
||||
if (scene.getRoot() instanceof Pane pane) {
|
||||
rootPane = pane;
|
||||
} else {
|
||||
// Controls like SplitPane do not accept extra children, so wrap them.
|
||||
rootPane = new BorderPane(scene.getRoot());
|
||||
scene.setRoot(rootPane);
|
||||
}
|
||||
|
||||
MenuBar sharedMenuBar = new MenuBar();
|
||||
sharedMenuBar.setUseSystemMenuBar(true);
|
||||
sharedMenuBar.setManaged(false);
|
||||
Bindings.bindContent(sharedMenuBar.getMenus(), mainScreenMenuBar.getMenus());
|
||||
rootPane.getChildren().add(sharedMenuBar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a scene whose stylesheet follows the light/dark design switch and
|
||||
* applies the currently active design to it.
|
||||
@@ -5796,6 +5841,22 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
: ApplicationConstants.STYLECSSFILE_DEFAULT_DAYLIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the macOS connection state menu, e.g. "🟢 LINK: Connected".
|
||||
*/
|
||||
static String macOsConnectionStateMenuTitle(On4KstConnectionState state) {
|
||||
On4KstConnectionState effectiveState = state == null
|
||||
? On4KstConnectionState.DISCONNECTED : state;
|
||||
return switch (effectiveState) {
|
||||
case ONLINE -> "🟢 LINK: Connected";
|
||||
case CONNECTING, WAITING_FOR_LOGIN_PROMPT, AUTHENTICATING,
|
||||
SYNCING_MAIN_CHAT, SYNCING_SECOND_CHAT -> "🟡 LINK: Connecting…";
|
||||
case STOPPING -> "🟡 LINK: Disconnecting…";
|
||||
case RECONNECT_WAIT -> "🔴 LINK: Reconnecting…";
|
||||
case DISCONNECTED -> "🔴 LINK: Disconnected";
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************
|
||||
* Sked warning Initializing and functional section
|
||||
@@ -5864,6 +5925,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
tipConnectionStateIndicator.setText(
|
||||
"ON4KST link: " + effectiveState.name() + "\n" + stateDetail);
|
||||
btnConnectionStateIndicator.setAccessibleHelp(stateDetail);
|
||||
menuConnectionStateMacOs.setText(macOsConnectionStateMenuTitle(effectiveState));
|
||||
menuItemConnectionStateDetailMacOs.setText(stateDetail);
|
||||
|
||||
String commonStyle =
|
||||
"-fx-font-size: 10px;"
|
||||
@@ -7178,14 +7241,17 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
|
||||
|
||||
|
||||
MenuBar mainScreenMenuBar = initMenuBar();
|
||||
mainScreenMenuBar = initMenuBar();
|
||||
flwpne_StatusBar = new FlowPane();
|
||||
|
||||
flwpne_StatusBar.getChildren().add(mainScreenMenuBar);
|
||||
bPaneChatWindow.setTop(flwpne_StatusBar);
|
||||
|
||||
initConnectionStateIndicatorButton();
|
||||
flwpne_StatusBar.getChildren().add(btnConnectionStateIndicator);
|
||||
// On macOS the connection state is shown in the system menu bar instead.
|
||||
if (!PlatformUtils.isMacOs()) {
|
||||
flwpne_StatusBar.getChildren().add(btnConnectionStateIndicator);
|
||||
}
|
||||
|
||||
initSkedWarnIndicatorButton();
|
||||
|
||||
@@ -9165,6 +9231,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
}
|
||||
});
|
||||
|
||||
installSharedSystemMenuBar(clusterAndQSOMonScene);
|
||||
clusterAndQSOMonStage.setScene(clusterAndQSOMonScene);
|
||||
clusterAndQSOMonStage.show();
|
||||
|
||||
@@ -9276,6 +9343,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
|
||||
stage_updateStage.setScene(new Scene(vbxUpdateWindow, chatcontroller.getChatPreferences().getGUIstage_updateStage_SceneSizeHW()[0], chatcontroller.getChatPreferences().getGUIstage_updateStage_SceneSizeHW()[1]));
|
||||
registerThemedScene(stage_updateStage.getScene());
|
||||
installSharedSystemMenuBar(stage_updateStage.getScene());
|
||||
stage_updateStage.getScene().widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
chatcontroller.getChatPreferences().getGUIstage_updateStage_SceneSizeHW()[0] = newValue.doubleValue();
|
||||
requestLayoutSave();
|
||||
@@ -12347,6 +12415,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
|
||||
requestLayoutSave();
|
||||
});
|
||||
|
||||
installSharedSystemMenuBar(settingsScene);
|
||||
settingsStage.setScene(settingsScene);
|
||||
|
||||
// settingsStage.getScene().getWindow().addEventFilter(WindowEvent.WINDOW_CLOSE_REQUEST, this::closeWindowEvent);
|
||||
|
||||
@@ -277,6 +277,13 @@ public final class StationMapView {
|
||||
return stage.isShowing();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map window scene, e.g. to attach the shared macOS system menu bar
|
||||
*/
|
||||
public Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public void applyThemeFromPreferences() {
|
||||
boolean darkMode = chatPreferences.isGUI_darkModeActive();
|
||||
applySceneTheme(darkMode);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package kst4contest.test;
|
||||
|
||||
import kst4contest.utils.PlatformUtils;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class PlatformUtilsTest {
|
||||
|
||||
@Test
|
||||
void macOsNamesAreDetected() {
|
||||
assertTrue(PlatformUtils.isMacOs("Mac OS X"));
|
||||
assertTrue(PlatformUtils.isMacOs("macOS"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void otherOrMissingNamesAreNotMacOs() {
|
||||
assertFalse(PlatformUtils.isMacOs("Windows 11"));
|
||||
assertFalse(PlatformUtils.isMacOs("Linux"));
|
||||
assertFalse(PlatformUtils.isMacOs(""));
|
||||
assertFalse(PlatformUtils.isMacOs(null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package kst4contest.view;
|
||||
|
||||
import kst4contest.controller.On4KstConnectionState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class MacOsConnectionStateMenuTitleTest {
|
||||
|
||||
@Test
|
||||
void everyStateHasTheAgreedTitle() {
|
||||
assertEquals("🟢 LINK: Connected",
|
||||
Kst4ContestApplication.macOsConnectionStateMenuTitle(On4KstConnectionState.ONLINE));
|
||||
for (On4KstConnectionState state : new On4KstConnectionState[]{
|
||||
On4KstConnectionState.CONNECTING,
|
||||
On4KstConnectionState.WAITING_FOR_LOGIN_PROMPT,
|
||||
On4KstConnectionState.AUTHENTICATING,
|
||||
On4KstConnectionState.SYNCING_MAIN_CHAT,
|
||||
On4KstConnectionState.SYNCING_SECOND_CHAT}) {
|
||||
assertEquals("🟡 LINK: Connecting…", Kst4ContestApplication.macOsConnectionStateMenuTitle(state));
|
||||
}
|
||||
assertEquals("🟡 LINK: Disconnecting…",
|
||||
Kst4ContestApplication.macOsConnectionStateMenuTitle(On4KstConnectionState.STOPPING));
|
||||
assertEquals("🔴 LINK: Reconnecting…",
|
||||
Kst4ContestApplication.macOsConnectionStateMenuTitle(On4KstConnectionState.RECONNECT_WAIT));
|
||||
assertEquals("🔴 LINK: Disconnected",
|
||||
Kst4ContestApplication.macOsConnectionStateMenuTitle(On4KstConnectionState.DISCONNECTED));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingStateIsShownAsDisconnected() {
|
||||
assertEquals("🔴 LINK: Disconnected", Kst4ContestApplication.macOsConnectionStateMenuTitle(null));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user