From 8a87184f22166711f4cc8ec5b8fb931bdf9f4c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Fedi=C4=8D?= <fedic.patrik@gmail.com> Date: Mon, 6 Nov 2023 08:17:49 +0100 Subject: [PATCH 1/4] TripleA-2.6.929 show Zoom percentage request #10693 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Fedič <fedic.patrik@gmail.com> --- .../games/strategy/engine/data/GameData.java | 52 +- .../engine/data/events/ZoomMapListener.java | 8 + .../games/strategy/triplea/ui/BottomBar.java | 106 +- .../strategy/triplea/ui/menubar/ViewMenu.java | 928 +++++++++--------- 4 files changed, 564 insertions(+), 530 deletions(-) create mode 100644 game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java b/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java index 980b6c362d..97a97794c3 100644 --- a/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java @@ -4,6 +4,7 @@ import com.google.common.base.MoreObjects; import games.strategy.engine.data.events.GameDataChangeListener; import games.strategy.engine.data.events.TerritoryListener; +import games.strategy.engine.data.events.ZoomMapListener; import games.strategy.engine.data.properties.GameProperties; import games.strategy.engine.delegate.IDelegate; import games.strategy.engine.framework.GameDataManager; @@ -80,6 +81,7 @@ public class GameData implements Serializable, GameState { @RemoveOnNextMajorRelease @Deprecated private Version gameVersion; private int diceSides; private transient List<TerritoryListener> territoryListeners = new CopyOnWriteArrayList<>(); + private final transient List<ZoomMapListener> zoomMapListeners = new CopyOnWriteArrayList<>(); private transient List<GameDataChangeListener> dataChangeListeners = new CopyOnWriteArrayList<>(); private transient Map<String, IDelegate> delegates = new HashMap<>(); private final AllianceTracker alliances = new AllianceTracker(); @@ -101,12 +103,12 @@ public class GameData implements Serializable, GameState { private final GameProperties properties = new GameProperties(this); private final UnitsList unitsList = new UnitsList(); private final TechnologyFrontier technologyFrontier = - new TechnologyFrontier("allTechsForGame", this); + new TechnologyFrontier("allTechsForGame", this); @Getter private transient TechTracker techTracker = new TechTracker(this); private final IGameLoader loader = new TripleA(); private History gameHistory = new History(this); private List<Tuple<IAttachment, List<Tuple<String, String>>>> attachmentOrderAndValues = - new ArrayList<>(); + new ArrayList<>(); private final Map<String, TerritoryEffect> territoryEffectList = new HashMap<>(); private final BattleRecordsList battleRecordsList = new BattleRecordsList(this); private transient GameDataEventListeners gameDataEventListeners = new GameDataEventListeners(); @@ -258,6 +260,10 @@ public GameProperties getProperties() { return properties; } + public void addZoomMapListeners(final ZoomMapListener listener) { + zoomMapListeners.add(listener); + } + public void addTerritoryListener(final TerritoryListener listener) { territoryListeners.add(listener); } @@ -284,6 +290,10 @@ void notifyTerritoryUnitsChanged(final Territory t) { territoryListeners.forEach(territoryListener -> territoryListener.unitsChanged(t)); } + public void notifyMapZoomChanged(Integer newZoom) { + zoomMapListeners.forEach(zoomMapListener -> zoomMapListener.zoomMapChanged(newZoom)); + } + void notifyTerritoryAttachmentChanged(final Territory t) { territoryListeners.forEach(territoryListener -> territoryListener.attachmentChanged(t)); } @@ -361,9 +371,9 @@ public void resetHistory() { final boolean oldForceInSwingEventThread = forceInSwingEventThread; forceInSwingEventThread = false; gameHistory - .getHistoryWriter() - .startNextStep( - step.getName(), step.getDelegateName(), step.getPlayerId(), step.getDisplayName()); + .getHistoryWriter() + .startNextStep( + step.getName(), step.getDelegateName(), step.getPlayerId(), step.getDisplayName()); forceInSwingEventThread = oldForceInSwingEventThread; } @@ -446,7 +456,7 @@ private static Unlocker acquireLock(Lock lock) { } public void addToAttachmentOrderAndValues( - final Tuple<IAttachment, List<Tuple<String, String>>> attachmentAndValues) { + final Tuple<IAttachment, List<Tuple<String, String>>> attachmentAndValues) { attachmentOrderAndValues.add(attachmentAndValues); } @@ -455,7 +465,7 @@ public List<Tuple<IAttachment, List<Tuple<String, String>>>> getAttachmentOrderA } public void setAttachmentOrderAndValues( - List<Tuple<IAttachment, List<Tuple<String, String>>>> values) { + List<Tuple<IAttachment, List<Tuple<String, String>>>> values) { attachmentOrderAndValues = values; } @@ -520,12 +530,12 @@ public TechnologyDelegate getTechDelegate() { public void preGameDisablePlayers(final Predicate<GamePlayer> shouldDisablePlayer) { final Set<GamePlayer> playersWhoShouldBeRemoved = new HashSet<>(); playerList.getPlayers().stream() - .filter(p -> (p.getCanBeDisabled() && shouldDisablePlayer.test(p))) - .forEach( - p -> { - p.setIsDisabled(true); - playersWhoShouldBeRemoved.add(p); - }); + .filter(p -> (p.getCanBeDisabled() && shouldDisablePlayer.test(p))) + .forEach( + p -> { + p.setIsDisabled(true); + playersWhoShouldBeRemoved.add(p); + }); if (!playersWhoShouldBeRemoved.isEmpty()) { removePlayerStepsFromSequence(playersWhoShouldBeRemoved); } @@ -564,12 +574,12 @@ public void performChange(final Change change) { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("diceSides", diceSides) - .add("gameName", gameName) - .add("gameVersion", gameVersion) - .add("loader", loader) - .add("playerList", playerList) - .toString(); + .add("diceSides", diceSides) + .add("gameName", gameName) + .add("gameVersion", gameVersion) + .add("loader", loader) + .add("playerList", playerList) + .toString(); } /** @@ -603,11 +613,11 @@ public String loadGameNotes(final Path mapLocation) { public Optional<Path> getGameXmlPath(final Path mapLocation) { // Given a game name, the map.yml file can tell us the path to the game xml file. return findMapDescriptionYaml(mapLocation) - .flatMap(yaml -> yaml.getGameXmlPathByGameName(getGameName())); + .flatMap(yaml -> yaml.getGameXmlPathByGameName(getGameName())); } private Optional<MapDescriptionYaml> findMapDescriptionYaml(final Path mapLocation) { return FileUtils.findFileInParentFolders(mapLocation, MapDescriptionYaml.MAP_YAML_FILE_NAME) - .flatMap(MapDescriptionYaml::fromFile); + .flatMap(MapDescriptionYaml::fromFile); } } diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java b/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java new file mode 100644 index 0000000000..e3482983b3 --- /dev/null +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java @@ -0,0 +1,8 @@ +package games.strategy.engine.data.events; + +/** + * A ZoomMapListener will be notified of events that affect a map zoom in ViewMenu in onClick on OK button. + * */ +public interface ZoomMapListener { + void zoomMapChanged(Integer newZoom); +} diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java index 48a0ee858d..b1c90eb3bc 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java @@ -6,6 +6,7 @@ import games.strategy.engine.data.Territory; import games.strategy.engine.data.TerritoryEffect; import games.strategy.engine.data.events.TerritoryListener; +import games.strategy.engine.data.events.ZoomMapListener; import games.strategy.triplea.Constants; import games.strategy.triplea.attachments.TerritoryAttachment; import games.strategy.triplea.util.UnitCategory; @@ -17,6 +18,7 @@ import java.awt.Image; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -43,7 +45,7 @@ import org.triplea.swing.jpanel.GridBagConstraintsFill; @Slf4j -public class BottomBar extends JPanel implements TerritoryListener { +public class BottomBar extends JPanel implements TerritoryListener, ZoomMapListener { private final UiContext uiContext; private final ResourceBar resourceBar; @@ -55,6 +57,7 @@ public class BottomBar extends JPanel implements TerritoryListener { private final JLabel playerLabel = new JLabel("xxxxxx"); private final JLabel stepLabel = new JLabel("xxxxxx"); private final JLabel roundLabel = new JLabel("xxxxxx"); + private final JLabel zoomLabel = new JLabel("Zoom: 100%"); public BottomBar(final UiContext uiContext, final GameData data, final boolean usingDiceServer) { this.uiContext = uiContext; @@ -63,28 +66,32 @@ public BottomBar(final UiContext uiContext, final GameData data, final boolean u setLayout(new BorderLayout()); add(createCenterPanel(), BorderLayout.CENTER); add(createStepPanel(usingDiceServer), BorderLayout.EAST); + + data.addZoomMapListeners(this); } private JPanel createCenterPanel() { final JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridBagLayout()); final var gridBuilder = - new GridBagConstraintsBuilder().weightY(1).fill(GridBagConstraintsFill.BOTH); + new GridBagConstraintsBuilder().weightY(1).fill(GridBagConstraintsFill.BOTH); centerPanel.add( - resourceBar, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.WEST).build()); + resourceBar, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.WEST).build()); territoryInfo.setPreferredSize(new Dimension(0, 0)); territoryInfo.setBorder(new EtchedBorder(EtchedBorder.RAISED)); centerPanel.add( - territoryInfo, - gridBuilder.gridX(1).weightX(1).anchor(GridBagConstraintsAnchor.CENTER).build()); + territoryInfo, + gridBuilder.gridX(1).weightX(1).anchor(GridBagConstraintsAnchor.CENTER).build()); statusMessage.setVisible(false); statusMessage.setPreferredSize(new Dimension(0, 0)); statusMessage.setBorder(new EtchedBorder(EtchedBorder.RAISED)); centerPanel.add( - statusMessage, gridBuilder.gridX(2).anchor(GridBagConstraintsAnchor.EAST).build()); + statusMessage, gridBuilder.gridX(2).anchor(GridBagConstraintsAnchor.EAST).build()); + centerPanel.add( + zoomLabel, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.EAST).build()); return centerPanel; } @@ -123,10 +130,10 @@ public void setTerritory(final @Nullable Territory territory) { if (territory == null) { SwingUtilities.invokeLater( - () -> { - territoryInfo.removeAll(); - SwingComponents.redraw(territoryInfo); - }); + () -> { + territoryInfo.removeAll(); + SwingComponents.redraw(territoryInfo); + }); return; } @@ -134,9 +141,9 @@ public void setTerritory(final @Nullable Territory territory) { try (GameData.Unlocker ignored = territory.getData().acquireReadLock()) { final String territoryName = territory.getName(); final Collection<UnitCategory> units = - uiContext.isShowUnitsInStatusBar() - ? UnitSeparator.categorize(territory.getUnits()) - : List.of(); + uiContext.isShowUnitsInStatusBar() + ? UnitSeparator.categorize(territory.getUnits()) + : List.of(); final TerritoryAttachment ta = TerritoryAttachment.get(territory); final IntegerMap<Resource> resources = new IntegerMap<>(); final List<String> territoryEffectNames; @@ -144,9 +151,9 @@ public void setTerritory(final @Nullable Territory territory) { territoryEffectNames = List.of(); } else { territoryEffectNames = - ta.getTerritoryEffect().stream() - .map(TerritoryEffect::getName) - .collect(Collectors.toList()); + ta.getTerritoryEffect().stream() + .map(TerritoryEffect::getName) + .collect(Collectors.toList()); final int production = ta.getProduction(); if (production > 0) { resources.add(new Resource(Constants.PUS, territory.getData()), production); @@ -155,15 +162,15 @@ public void setTerritory(final @Nullable Territory territory) { } SwingUtilities.invokeLater( - () -> updateTerritoryInfo(territoryName, territoryEffectNames, units, resources)); + () -> updateTerritoryInfo(territoryName, territoryEffectNames, units, resources)); } } private void updateTerritoryInfo( - String territoryName, - List<String> territoryEffectNames, - Collection<UnitCategory> units, - IntegerMap<Resource> resources) { + String territoryName, + List<String> territoryEffectNames, + Collection<UnitCategory> units, + IntegerMap<Resource> resources) { // Box layout with horizontal glue on both sides achieves the following desirable properties: // 1. If the content is narrower than the available space, it will be centered. // 2. If the content is wider than the available space, then the beginning will be shown, @@ -246,7 +253,7 @@ public void gameDataChanged() { } public void setStepInfo( - int roundNumber, String stepName, @Nullable GamePlayer player, boolean isRemotePlayer) { + int roundNumber, String stepName, @Nullable GamePlayer player, boolean isRemotePlayer) { roundLabel.setText("Round:" + roundNumber + " "); stepLabel.setText(stepName); if (player != null) { @@ -256,11 +263,11 @@ public void setStepInfo( public void setCurrentPlayer(GamePlayer player, boolean isRemotePlayer) { final CompletableFuture<?> future = - CompletableFuture.supplyAsync(() -> uiContext.getFlagImageFactory().getFlag(player)) - .thenApplyAsync(ImageIcon::new) - .thenAccept(icon -> SwingUtilities.invokeLater(() -> roundLabel.setIcon(icon))); + CompletableFuture.supplyAsync(() -> uiContext.getFlagImageFactory().getFlag(player)) + .thenApplyAsync(ImageIcon::new) + .thenAccept(icon -> SwingUtilities.invokeLater(() -> roundLabel.setIcon(icon))); CompletableFutureUtils.logExceptionWhenComplete( - future, throwable -> log.error("Failed to set round icon for " + player, throwable)); + future, throwable -> log.error("Failed to set round icon for " + player, throwable)); playerLabel.setText((isRemotePlayer ? "REMOTE: " : "") + player.getName()); } @@ -268,26 +275,26 @@ private void listenForTerritoryUpdates(@Nullable Territory territory) { // Run async, as this is called while holding a GameData lock so we shouldn't grab a different // data's lock in this case. AsyncRunner.runAsync( - () -> { - GameData oldGameData = currentTerritory != null ? currentTerritory.getData() : null; - GameData newGameData = territory != null ? territory.getData() : null; - // Re-subscribe listener on the right GameData, which could change when toggling - // between history and the current game. - if (!ObjectUtils.referenceEquals(oldGameData, newGameData)) { - if (oldGameData != null) { - try (GameData.Unlocker ignored = oldGameData.acquireWriteLock()) { - oldGameData.removeTerritoryListener(this); - } - } - if (newGameData != null) { - try (GameData.Unlocker ignored = newGameData.acquireWriteLock()) { - newGameData.addTerritoryListener(this); - } - } - } - currentTerritory = territory; - }) - .exceptionally(e -> log.error("Territory listener error:", e)); + () -> { + GameData oldGameData = currentTerritory != null ? currentTerritory.getData() : null; + GameData newGameData = territory != null ? territory.getData() : null; + // Re-subscribe listener on the right GameData, which could change when toggling + // between history and the current game. + if (!ObjectUtils.referenceEquals(oldGameData, newGameData)) { + if (oldGameData != null) { + try (GameData.Unlocker ignored = oldGameData.acquireWriteLock()) { + oldGameData.removeTerritoryListener(this); + } + } + if (newGameData != null) { + try (GameData.Unlocker ignored = newGameData.acquireWriteLock()) { + newGameData.addTerritoryListener(this); + } + } + } + currentTerritory = territory; + }) + .exceptionally(e -> log.error("Territory listener error:", e)); } @Override @@ -302,4 +309,11 @@ public void ownerChanged(Territory territory) {} @Override public void attachmentChanged(Territory territory) {} + + @Override + public void zoomMapChanged(Integer newZoom) { + if (Objects.nonNull(newZoom)) { + zoomLabel.setText(String.format("Zoom: %d%%", newZoom)); + } + } } diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java index b7d5e9d03c..a7144cb103 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java @@ -52,486 +52,488 @@ @Slf4j final class ViewMenu extends JMenu { - private static final long serialVersionUID = -4703734404422047487L; + private static final long serialVersionUID = -4703734404422047487L; + + private JCheckBoxMenuItem showMapDetails; + private JCheckBoxMenuItem showMapBlends; + + private final List<Territory> gameMapTerritories; + private final TripleAFrame frame; + private final UiContext uiContext; + + ViewMenu(final TripleAFrame frame) { + super("View"); + + this.frame = frame; + this.uiContext = frame.getUiContext(); + gameMapTerritories = frame.getGame().getData().getMap().getTerritories(); + + setMnemonic(KeyEvent.VK_V); + + addZoomMenu(); + addUnitSizeMenu(); + addLockMap(); + addShowUnitsMenu(); + addShowUnitsInStatusBarMenu(); + addFlagDisplayModeMenu(); + + if (uiContext.getMapData().useTerritoryEffectMarkers()) { + addShowTerritoryEffects(); + } + if (ClientSetting.showBetaFeatures.getValueOrThrow()) { + addMapSkinsMenu(); + } + addShowMapDetails(); + addShowMapBlends(); + addMapFontAndColorEditorMenu(); + addChatTimeMenu(); + addShowCommentLog(); + addSeparator(); + addFindTerritory(); + + showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); + } - private JCheckBoxMenuItem showMapDetails; - private JCheckBoxMenuItem showMapBlends; + private void addShowCommentLog() { + add( + new JMenuItemCheckBoxBuilder("Show Comment Log", 'L') + .bindSetting(ClientSetting.showCommentLog) + .actionListener( + value -> { + if (value) { + frame.showCommentLog(); + } else { + frame.hideCommentLog(); + } + }) + .build()); + } - private final List<Territory> gameMapTerritories; - private final TripleAFrame frame; - private final UiContext uiContext; + private void addZoomMenu() { + final Action mapZoom = + SwingAction.of( + "Map Zoom", + e -> { + final SpinnerNumberModel model = new SpinnerNumberModel(); + model.setMaximum(UiContext.MAP_SCALE_MAX_VALUE * 100); + model.setMinimum(Math.ceil(frame.getMapPanel().getMinScale() * 100)); + model.setStepSize(1); + model.setValue((double) Math.round(frame.getMapPanel().getScale() * 100)); + final JSpinner spinner = new JSpinner(model); + final JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(new JLabel("Choose Map Zoom (%)"), BorderLayout.NORTH); + panel.add(spinner, BorderLayout.CENTER); + final JPanel buttons = new JPanel(); + final JButton fitWidth = new JButton("Fit Width"); + buttons.add(fitWidth); + final JButton fitHeight = new JButton("Fit Height"); + buttons.add(fitHeight); + final JButton reset = new JButton("Reset"); + buttons.add(reset); + panel.add(buttons, BorderLayout.SOUTH); + fitWidth.addActionListener( + event -> { + final double screenWidth = frame.getMapPanel().getWidth(); + final double mapWidth = frame.getMapPanel().getImageWidth(); + double ratio = screenWidth / mapWidth; + ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); + ratio = Math.min(1, ratio); + model.setValue((int) Math.round(ratio * 100)); + }); + fitHeight.addActionListener( + event -> { + final double screenHeight = frame.getMapPanel().getHeight(); + final double mapHeight = frame.getMapPanel().getImageHeight(); + double ratio = screenHeight / mapHeight; + ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); + model.setValue((int) Math.round(ratio * 100)); + }); + reset.addActionListener(event -> model.setValue(100)); + final int result = + JOptionPane.showOptionDialog( + frame, + panel, + "Choose Map Zoom", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + new String[] {"OK", "Cancel"}, + 0); + if (result != 0) { + return; + } + final Number value = (Number) model.getValue(); + frame.getMapPanel().setScale(value.doubleValue() / 100); + + frame.getGame().getData().notifyMapZoomChanged(value.intValue()); + }); + add(mapZoom).setMnemonic(KeyEvent.VK_Z); + } - ViewMenu(final TripleAFrame frame) { - super("View"); + private void addUnitSizeMenu() { + final NumberFormat decimalFormat = new DecimalFormat("00.##"); + // This is the action listener used + class UnitSizeAction extends AbstractAction { + private static final long serialVersionUID = -6280511505686687867L; + private final double scaleFactor; - this.frame = frame; - this.uiContext = frame.getUiContext(); - gameMapTerritories = frame.getGame().getData().getMap().getTerritories(); + private UnitSizeAction(final double scaleFactor) { + super(decimalFormat.format(scaleFactor * 100) + "%"); + this.scaleFactor = scaleFactor; + } - setMnemonic(KeyEvent.VK_V); + @Override + public void actionPerformed(final ActionEvent e) { + uiContext.setUnitScaleFactor(scaleFactor); + frame.getMapPanel().resetMap(); + } + } + + final JMenu unitSizeMenu = new JMenu(); + unitSizeMenu.setMnemonic(KeyEvent.VK_S); + unitSizeMenu.setText("Unit Size"); + final ButtonGroup unitSizeGroup = new ButtonGroup(); + final JRadioButtonMenuItem radioItem125 = new JRadioButtonMenuItem(new UnitSizeAction(1.25)); + final JRadioButtonMenuItem radioItem100 = new JRadioButtonMenuItem(new UnitSizeAction(1.0)); + radioItem100.setMnemonic(KeyEvent.VK_1); + final JRadioButtonMenuItem radioItem87 = new JRadioButtonMenuItem(new UnitSizeAction(0.875)); + final JRadioButtonMenuItem radioItem83 = new JRadioButtonMenuItem(new UnitSizeAction(0.8333)); + radioItem83.setMnemonic(KeyEvent.VK_8); + final JRadioButtonMenuItem radioItem75 = new JRadioButtonMenuItem(new UnitSizeAction(0.75)); + radioItem75.setMnemonic(KeyEvent.VK_7); + final JRadioButtonMenuItem radioItem66 = new JRadioButtonMenuItem(new UnitSizeAction(0.6666)); + radioItem66.setMnemonic(KeyEvent.VK_6); + final JRadioButtonMenuItem radioItem56 = new JRadioButtonMenuItem(new UnitSizeAction(0.5625)); + final JRadioButtonMenuItem radioItem50 = new JRadioButtonMenuItem(new UnitSizeAction(0.5)); + radioItem50.setMnemonic(KeyEvent.VK_5); + unitSizeGroup.add(radioItem125); + unitSizeGroup.add(radioItem100); + unitSizeGroup.add(radioItem87); + unitSizeGroup.add(radioItem83); + unitSizeGroup.add(radioItem75); + unitSizeGroup.add(radioItem66); + unitSizeGroup.add(radioItem56); + unitSizeGroup.add(radioItem50); + radioItem100.setSelected(true); + // select the closest to the default size + final Enumeration<AbstractButton> enum1 = unitSizeGroup.getElements(); + boolean matchFound = false; + while (enum1.hasMoreElements()) { + final JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) enum1.nextElement(); + final UnitSizeAction action = (UnitSizeAction) menuItem.getAction(); + if (Math.abs(action.scaleFactor - uiContext.getUnitImageFactory().getScaleFactor()) < 0.01) { + menuItem.setSelected(true); + matchFound = true; + break; + } + } + if (!matchFound) { + log.error("default unit size does not match any menu item"); + } + unitSizeMenu.add(radioItem125); + unitSizeMenu.add(radioItem100); + unitSizeMenu.add(radioItem87); + unitSizeMenu.add(radioItem83); + unitSizeMenu.add(radioItem75); + unitSizeMenu.add(radioItem66); + unitSizeMenu.add(radioItem56); + unitSizeMenu.add(radioItem50); + add(unitSizeMenu); + } - addZoomMenu(); - addUnitSizeMenu(); - addLockMap(); - addShowUnitsMenu(); - addShowUnitsInStatusBarMenu(); - addFlagDisplayModeMenu(); + private void addMapSkinsMenu() { + final JMenu mapSubMenu = new JMenu("Map Skins"); + mapSubMenu.setMnemonic(KeyEvent.VK_K); + add(mapSubMenu); + final ButtonGroup mapButtonGroup = new ButtonGroup(); + final Collection<UiContext.MapSkin> skins = + uiContext.getSkins(frame.getGame().getData().getMapName()); + mapSubMenu.setEnabled(skins.size() > 1); + for (final UiContext.MapSkin mapSkin : skins) { + final JMenuItem mapMenuItem = new JRadioButtonMenuItem(mapSkin.getSkinName()); + mapButtonGroup.add(mapMenuItem); + mapSubMenu.add(mapMenuItem); + mapMenuItem.setSelected(mapSkin.isCurrentSkin()); + mapMenuItem.addActionListener( + e -> { + try { + frame.changeMapSkin(mapSkin.getSkinName()); + if (uiContext.getMapData().getHasRelief()) { + showMapDetails.setSelected(true); + } + showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); + } catch (final Exception exception) { + log.error("Error Changing Map Skin2", exception); + } + }); + } + } - if (uiContext.getMapData().useTerritoryEffectMarkers()) { - addShowTerritoryEffects(); + private void addShowMapDetails() { + showMapDetails = new JCheckBoxMenuItem("Show Map Details"); + showMapDetails.setMnemonic(KeyEvent.VK_D); + showMapDetails.setSelected(TileImageFactory.getShowReliefImages()); + showMapDetails.addActionListener( + e -> { + if (TileImageFactory.getShowReliefImages() == showMapDetails.isSelected()) { + return; + } + TileImageFactory.setShowReliefImages(showMapDetails.isSelected()); + ThreadRunner.runInNewThread( + () -> frame.getMapPanel().updateCountries(gameMapTerritories)); + }); + add(showMapDetails); } - if (ClientSetting.showBetaFeatures.getValueOrThrow()) { - addMapSkinsMenu(); + + private void addShowMapBlends() { + showMapBlends = new JCheckBoxMenuItem("Show Map Blends"); + showMapBlends.setMnemonic(KeyEvent.VK_B); + if (uiContext.getMapData().getHasRelief() + && showMapDetails.isEnabled() + && showMapDetails.isSelected()) { + showMapBlends.setEnabled(true); + showMapBlends.setSelected(TileImageFactory.getShowMapBlends()); + } else { + showMapBlends.setSelected(false); + showMapBlends.setEnabled(false); + } + showMapBlends.addActionListener( + e -> { + if (TileImageFactory.getShowMapBlends() == showMapBlends.isSelected()) { + return; + } + TileImageFactory.setShowMapBlends(showMapBlends.isSelected()); + TileImageFactory.setShowMapBlendMode(uiContext.getMapData().getMapBlendMode()); + TileImageFactory.setShowMapBlendAlpha(uiContext.getMapData().getMapBlendAlpha()); + new Thread( + () -> frame.getMapPanel().updateCountries(gameMapTerritories), + "Show map Blends thread") + .start(); + }); + add(showMapBlends); } - addShowMapDetails(); - addShowMapBlends(); - addMapFontAndColorEditorMenu(); - addChatTimeMenu(); - addShowCommentLog(); - addSeparator(); - addFindTerritory(); - - showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); - } - - private void addShowCommentLog() { - add( - new JMenuItemCheckBoxBuilder("Show Comment Log", 'L') - .bindSetting(ClientSetting.showCommentLog) - .actionListener( - value -> { - if (value) { - frame.showCommentLog(); - } else { - frame.hideCommentLog(); - } - }) - .build()); - } - - private void addZoomMenu() { - final Action mapZoom = - SwingAction.of( - "Map Zoom", - e -> { - final SpinnerNumberModel model = new SpinnerNumberModel(); - model.setMaximum(UiContext.MAP_SCALE_MAX_VALUE * 100); - model.setMinimum(Math.ceil(frame.getMapPanel().getMinScale() * 100)); - model.setStepSize(1); - model.setValue((double) Math.round(frame.getMapPanel().getScale() * 100)); - final JSpinner spinner = new JSpinner(model); - final JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(new JLabel("Choose Map Zoom (%)"), BorderLayout.NORTH); - panel.add(spinner, BorderLayout.CENTER); - final JPanel buttons = new JPanel(); - final JButton fitWidth = new JButton("Fit Width"); - buttons.add(fitWidth); - final JButton fitHeight = new JButton("Fit Height"); - buttons.add(fitHeight); - final JButton reset = new JButton("Reset"); - buttons.add(reset); - panel.add(buttons, BorderLayout.SOUTH); - fitWidth.addActionListener( - event -> { - final double screenWidth = frame.getMapPanel().getWidth(); - final double mapWidth = frame.getMapPanel().getImageWidth(); - double ratio = screenWidth / mapWidth; - ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); - ratio = Math.min(1, ratio); - model.setValue((int) Math.round(ratio * 100)); - }); - fitHeight.addActionListener( - event -> { - final double screenHeight = frame.getMapPanel().getHeight(); - final double mapHeight = frame.getMapPanel().getImageHeight(); - double ratio = screenHeight / mapHeight; - ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); - model.setValue((int) Math.round(ratio * 100)); - }); - reset.addActionListener(event -> model.setValue(100)); - final int result = - JOptionPane.showOptionDialog( - frame, - panel, - "Choose Map Zoom", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - new String[] {"OK", "Cancel"}, - 0); - if (result != 0) { - return; - } - final Number value = (Number) model.getValue(); - frame.getMapPanel().setScale(value.doubleValue() / 100); - }); - add(mapZoom).setMnemonic(KeyEvent.VK_Z); - } - - private void addUnitSizeMenu() { - final NumberFormat decimalFormat = new DecimalFormat("00.##"); - // This is the action listener used - class UnitSizeAction extends AbstractAction { - private static final long serialVersionUID = -6280511505686687867L; - private final double scaleFactor; - - private UnitSizeAction(final double scaleFactor) { - super(decimalFormat.format(scaleFactor * 100) + "%"); - this.scaleFactor = scaleFactor; - } - - @Override - public void actionPerformed(final ActionEvent e) { - uiContext.setUnitScaleFactor(scaleFactor); - frame.getMapPanel().resetMap(); - } + + private void addShowUnitsMenu() { + final JCheckBoxMenuItem showUnitsBox = new JCheckBoxMenuItem("Show Units"); + showUnitsBox.setMnemonic(KeyEvent.VK_U); + showUnitsBox.setSelected(true); + showUnitsBox.addActionListener( + e -> { + uiContext.setShowUnits(showUnitsBox.isSelected()); + frame.getMapPanel().resetMap(); + }); + add(showUnitsBox); } - final JMenu unitSizeMenu = new JMenu(); - unitSizeMenu.setMnemonic(KeyEvent.VK_S); - unitSizeMenu.setText("Unit Size"); - final ButtonGroup unitSizeGroup = new ButtonGroup(); - final JRadioButtonMenuItem radioItem125 = new JRadioButtonMenuItem(new UnitSizeAction(1.25)); - final JRadioButtonMenuItem radioItem100 = new JRadioButtonMenuItem(new UnitSizeAction(1.0)); - radioItem100.setMnemonic(KeyEvent.VK_1); - final JRadioButtonMenuItem radioItem87 = new JRadioButtonMenuItem(new UnitSizeAction(0.875)); - final JRadioButtonMenuItem radioItem83 = new JRadioButtonMenuItem(new UnitSizeAction(0.8333)); - radioItem83.setMnemonic(KeyEvent.VK_8); - final JRadioButtonMenuItem radioItem75 = new JRadioButtonMenuItem(new UnitSizeAction(0.75)); - radioItem75.setMnemonic(KeyEvent.VK_7); - final JRadioButtonMenuItem radioItem66 = new JRadioButtonMenuItem(new UnitSizeAction(0.6666)); - radioItem66.setMnemonic(KeyEvent.VK_6); - final JRadioButtonMenuItem radioItem56 = new JRadioButtonMenuItem(new UnitSizeAction(0.5625)); - final JRadioButtonMenuItem radioItem50 = new JRadioButtonMenuItem(new UnitSizeAction(0.5)); - radioItem50.setMnemonic(KeyEvent.VK_5); - unitSizeGroup.add(radioItem125); - unitSizeGroup.add(radioItem100); - unitSizeGroup.add(radioItem87); - unitSizeGroup.add(radioItem83); - unitSizeGroup.add(radioItem75); - unitSizeGroup.add(radioItem66); - unitSizeGroup.add(radioItem56); - unitSizeGroup.add(radioItem50); - radioItem100.setSelected(true); - // select the closest to the default size - final Enumeration<AbstractButton> enum1 = unitSizeGroup.getElements(); - boolean matchFound = false; - while (enum1.hasMoreElements()) { - final JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) enum1.nextElement(); - final UnitSizeAction action = (UnitSizeAction) menuItem.getAction(); - if (Math.abs(action.scaleFactor - uiContext.getUnitImageFactory().getScaleFactor()) < 0.01) { - menuItem.setSelected(true); - matchFound = true; - break; - } + private void addShowUnitsInStatusBarMenu() { + JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem("Show Units in Status Bar"); + checkbox.setSelected(true); + checkbox.addActionListener( + e -> { + uiContext.setShowUnitsInStatusBar(checkbox.isSelected()); + // Trigger a bottom bar update. + frame.getBottomBar().setTerritory(frame.getMapPanel().getCurrentTerritory()); + }); + add(checkbox); } - if (!matchFound) { - log.error("default unit size does not match any menu item"); + + private void addMapFontAndColorEditorMenu() { + final Action mapFontOptions = + SwingAction.of( + "Map Font and Color", + e -> { + final List<IEditableProperty<?>> properties = new ArrayList<>(); + final NumberProperty fontsize = + new NumberProperty( + "Font Size", null, 60, 0, MapImage.getPropertyMapFont().getSize()); + final ColorProperty territoryNameColor = + new ColorProperty( + "Territory Name and PU Color", + null, + MapImage.getPropertyTerritoryNameAndPuAndCommentColor()); + final ColorProperty unitCountColor = + new ColorProperty("Unit Count Color", null, MapImage.getPropertyUnitCountColor()); + final ColorProperty unitCountOutline = + new ColorProperty( + "Unit Count Outline", null, MapImage.getPropertyUnitCountOutline()); + final ColorProperty factoryDamageColor = + new ColorProperty( + "Factory Damage Color", null, MapImage.getPropertyUnitFactoryDamageColor()); + final ColorProperty factoryDamageOutline = + new ColorProperty( + "Factory Damage Outline", + null, + MapImage.getPropertyUnitFactoryDamageOutline()); + final ColorProperty hitDamageColor = + new ColorProperty( + "Hit Damage Color", null, MapImage.getPropertyUnitHitDamageColor()); + final ColorProperty hitDamageOutline = + new ColorProperty( + "Hit Damage Outline", null, MapImage.getPropertyUnitHitDamageOutline()); + properties.add(fontsize); + properties.add(territoryNameColor); + properties.add(unitCountColor); + properties.add(unitCountOutline); + properties.add(factoryDamageColor); + properties.add(factoryDamageOutline); + properties.add(hitDamageColor); + properties.add(hitDamageOutline); + final PropertiesUi pui = new PropertiesUi(properties, true); + final JPanel ui = new JPanel(); + ui.setLayout(new BorderLayout()); + ui.add(pui, BorderLayout.CENTER); + ui.add( + new JLabel( + "<html>Change the font and color of 'text' (not pictures) on the map. " + + "<br /><em>(Some people encounter problems with the color picker, " + + "and this " + + "<br />is a bug outside of triplea, located in the 'look and feel' " + + "that " + + "<br />you are using. If you have an error come up, try switching to " + + "the " + + "<br />basic 'look and feel', then setting the color, then switching " + + "back.)</em></html>"), + BorderLayout.NORTH); + final Object[] options = {"Set Properties", "Reset To Default", "Cancel"}; + final int result = + JOptionPane.showOptionDialog( + frame, + ui, + "Map Font and Color", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + options, + 2); + if (result == 1) { + MapImage.resetPropertyMapFont(); + MapImage.resetPropertyTerritoryNameAndPuAndCommentColor(); + MapImage.resetPropertyUnitCountColor(); + MapImage.resetPropertyUnitCountOutline(); + MapImage.resetPropertyUnitFactoryDamageColor(); + MapImage.resetPropertyUnitFactoryDamageOutline(); + MapImage.resetPropertyUnitHitDamageColor(); + MapImage.resetPropertyUnitHitDamageOutline(); + frame.getMapPanel().resetMap(); + } else if (result == 0) { + MapImage.setPropertyMapFont(new Font("Arial", Font.BOLD, fontsize.getValue())); + MapImage.setPropertyTerritoryNameAndPuAndCommentColor( + territoryNameColor.getValue()); + MapImage.setPropertyUnitCountColor(unitCountColor.getValue()); + MapImage.setPropertyUnitCountOutline(unitCountOutline.getValue()); + MapImage.setPropertyUnitFactoryDamageColor(factoryDamageColor.getValue()); + MapImage.setPropertyUnitFactoryDamageOutline(factoryDamageOutline.getValue()); + MapImage.setPropertyUnitHitDamageColor(hitDamageColor.getValue()); + MapImage.setPropertyUnitHitDamageOutline(hitDamageOutline.getValue()); + frame.getMapPanel().resetMap(); + } + }); + add(mapFontOptions).setMnemonic(KeyEvent.VK_C); + } + + private void addShowTerritoryEffects() { + final JCheckBoxMenuItem territoryEffectsBox = new JCheckBoxMenuItem("Show TerritoryEffects"); + territoryEffectsBox.setMnemonic(KeyEvent.VK_T); + territoryEffectsBox.addActionListener( + e -> { + uiContext.setShowTerritoryEffects(territoryEffectsBox.isSelected()); + frame.getMapPanel().resetMap(); + }); + add(territoryEffectsBox); + territoryEffectsBox.setSelected(true); + } + + private void addLockMap() { + add( + new JMenuItemCheckBoxBuilder("Lock Map", 'M') + .accelerator(KeyCode.L) + .bindSetting(ClientSetting.lockMap) + .build()); } - unitSizeMenu.add(radioItem125); - unitSizeMenu.add(radioItem100); - unitSizeMenu.add(radioItem87); - unitSizeMenu.add(radioItem83); - unitSizeMenu.add(radioItem75); - unitSizeMenu.add(radioItem66); - unitSizeMenu.add(radioItem56); - unitSizeMenu.add(radioItem50); - add(unitSizeMenu); - } - - private void addMapSkinsMenu() { - final JMenu mapSubMenu = new JMenu("Map Skins"); - mapSubMenu.setMnemonic(KeyEvent.VK_K); - add(mapSubMenu); - final ButtonGroup mapButtonGroup = new ButtonGroup(); - final Collection<UiContext.MapSkin> skins = - uiContext.getSkins(frame.getGame().getData().getMapName()); - mapSubMenu.setEnabled(skins.size() > 1); - for (final UiContext.MapSkin mapSkin : skins) { - final JMenuItem mapMenuItem = new JRadioButtonMenuItem(mapSkin.getSkinName()); - mapButtonGroup.add(mapMenuItem); - mapSubMenu.add(mapMenuItem); - mapMenuItem.setSelected(mapSkin.isCurrentSkin()); - mapMenuItem.addActionListener( - e -> { + + private void addFlagDisplayModeMenu() { + // 2.0 to 1.9 compatibility hack. Can be removed when all players that have played a 2.0 + // prelease have launched a game containing this patch. When going from 2.0 to 1.9, + // 1.9 will crash due to an enum value not found error when loading 'DRAW_MODE' + final Preferences prefs = Preferences.userNodeForPackage(getClass()); + if (prefs.get("DRAW_MODE", null) != null) { + prefs.remove("DRAW_MODE"); try { - frame.changeMapSkin(mapSkin.getSkinName()); - if (uiContext.getMapData().getHasRelief()) { - showMapDetails.setSelected(true); - } - showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); - } catch (final Exception exception) { - log.error("Error Changing Map Skin2", exception); + prefs.flush(); + } catch (final BackingStoreException ignored) { + // ignore } - }); - } - } - - private void addShowMapDetails() { - showMapDetails = new JCheckBoxMenuItem("Show Map Details"); - showMapDetails.setMnemonic(KeyEvent.VK_D); - showMapDetails.setSelected(TileImageFactory.getShowReliefImages()); - showMapDetails.addActionListener( - e -> { - if (TileImageFactory.getShowReliefImages() == showMapDetails.isSelected()) { - return; - } - TileImageFactory.setShowReliefImages(showMapDetails.isSelected()); - ThreadRunner.runInNewThread( - () -> frame.getMapPanel().updateCountries(gameMapTerritories)); - }); - add(showMapDetails); - } - - private void addShowMapBlends() { - showMapBlends = new JCheckBoxMenuItem("Show Map Blends"); - showMapBlends.setMnemonic(KeyEvent.VK_B); - if (uiContext.getMapData().getHasRelief() - && showMapDetails.isEnabled() - && showMapDetails.isSelected()) { - showMapBlends.setEnabled(true); - showMapBlends.setSelected(TileImageFactory.getShowMapBlends()); - } else { - showMapBlends.setSelected(false); - showMapBlends.setEnabled(false); + } + + final JMenu flagDisplayMenu = new JMenu(); + flagDisplayMenu.setMnemonic(KeyEvent.VK_N); + flagDisplayMenu.setText("Flag Display"); + final ButtonGroup flagsDisplayGroup = new ButtonGroup(); + + final JRadioButtonMenuItem noFlags = + new JMenuItemBuilder("Off", KeyCode.O) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.NONE, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + final JRadioButtonMenuItem smallFlags = + new JMenuItemBuilder("Small", KeyCode.S) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + final JRadioButtonMenuItem largeFlags = + new JMenuItemBuilder("Large", KeyCode.L) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + flagDisplayMenu.add(noFlags); + flagDisplayMenu.add(smallFlags); + flagDisplayMenu.add(largeFlags); + + // Add a menu listener to update the checked state of the items, as the flag state + // may change externally (e.g. via UnitScroller UI). + flagDisplayMenu.addMenuListener( + new MenuListener() { + @Override + public void menuSelected(final MenuEvent e) { + final var drawModel = ClientSetting.unitFlagDrawMode.getValueOrThrow(); + noFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.NONE); + smallFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG); + largeFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG); + } + + @Override + public void menuDeselected(final MenuEvent e) {} + + @Override + public void menuCanceled(final MenuEvent e) {} + }); + add(flagDisplayMenu); } - showMapBlends.addActionListener( - e -> { - if (TileImageFactory.getShowMapBlends() == showMapBlends.isSelected()) { - return; - } - TileImageFactory.setShowMapBlends(showMapBlends.isSelected()); - TileImageFactory.setShowMapBlendMode(uiContext.getMapData().getMapBlendMode()); - TileImageFactory.setShowMapBlendAlpha(uiContext.getMapData().getMapBlendAlpha()); - new Thread( - () -> frame.getMapPanel().updateCountries(gameMapTerritories), - "Show map Blends thread") - .start(); - }); - add(showMapBlends); - } - - private void addShowUnitsMenu() { - final JCheckBoxMenuItem showUnitsBox = new JCheckBoxMenuItem("Show Units"); - showUnitsBox.setMnemonic(KeyEvent.VK_U); - showUnitsBox.setSelected(true); - showUnitsBox.addActionListener( - e -> { - uiContext.setShowUnits(showUnitsBox.isSelected()); - frame.getMapPanel().resetMap(); - }); - add(showUnitsBox); - } - - private void addShowUnitsInStatusBarMenu() { - JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem("Show Units in Status Bar"); - checkbox.setSelected(true); - checkbox.addActionListener( - e -> { - uiContext.setShowUnitsInStatusBar(checkbox.isSelected()); - // Trigger a bottom bar update. - frame.getBottomBar().setTerritory(frame.getMapPanel().getCurrentTerritory()); - }); - add(checkbox); - } - - private void addMapFontAndColorEditorMenu() { - final Action mapFontOptions = - SwingAction.of( - "Map Font and Color", - e -> { - final List<IEditableProperty<?>> properties = new ArrayList<>(); - final NumberProperty fontsize = - new NumberProperty( - "Font Size", null, 60, 0, MapImage.getPropertyMapFont().getSize()); - final ColorProperty territoryNameColor = - new ColorProperty( - "Territory Name and PU Color", - null, - MapImage.getPropertyTerritoryNameAndPuAndCommentColor()); - final ColorProperty unitCountColor = - new ColorProperty("Unit Count Color", null, MapImage.getPropertyUnitCountColor()); - final ColorProperty unitCountOutline = - new ColorProperty( - "Unit Count Outline", null, MapImage.getPropertyUnitCountOutline()); - final ColorProperty factoryDamageColor = - new ColorProperty( - "Factory Damage Color", null, MapImage.getPropertyUnitFactoryDamageColor()); - final ColorProperty factoryDamageOutline = - new ColorProperty( - "Factory Damage Outline", - null, - MapImage.getPropertyUnitFactoryDamageOutline()); - final ColorProperty hitDamageColor = - new ColorProperty( - "Hit Damage Color", null, MapImage.getPropertyUnitHitDamageColor()); - final ColorProperty hitDamageOutline = - new ColorProperty( - "Hit Damage Outline", null, MapImage.getPropertyUnitHitDamageOutline()); - properties.add(fontsize); - properties.add(territoryNameColor); - properties.add(unitCountColor); - properties.add(unitCountOutline); - properties.add(factoryDamageColor); - properties.add(factoryDamageOutline); - properties.add(hitDamageColor); - properties.add(hitDamageOutline); - final PropertiesUi pui = new PropertiesUi(properties, true); - final JPanel ui = new JPanel(); - ui.setLayout(new BorderLayout()); - ui.add(pui, BorderLayout.CENTER); - ui.add( - new JLabel( - "<html>Change the font and color of 'text' (not pictures) on the map. " - + "<br /><em>(Some people encounter problems with the color picker, " - + "and this " - + "<br />is a bug outside of triplea, located in the 'look and feel' " - + "that " - + "<br />you are using. If you have an error come up, try switching to " - + "the " - + "<br />basic 'look and feel', then setting the color, then switching " - + "back.)</em></html>"), - BorderLayout.NORTH); - final Object[] options = {"Set Properties", "Reset To Default", "Cancel"}; - final int result = - JOptionPane.showOptionDialog( - frame, - ui, - "Map Font and Color", - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - 2); - if (result == 1) { - MapImage.resetPropertyMapFont(); - MapImage.resetPropertyTerritoryNameAndPuAndCommentColor(); - MapImage.resetPropertyUnitCountColor(); - MapImage.resetPropertyUnitCountOutline(); - MapImage.resetPropertyUnitFactoryDamageColor(); - MapImage.resetPropertyUnitFactoryDamageOutline(); - MapImage.resetPropertyUnitHitDamageColor(); - MapImage.resetPropertyUnitHitDamageOutline(); - frame.getMapPanel().resetMap(); - } else if (result == 0) { - MapImage.setPropertyMapFont(new Font("Arial", Font.BOLD, fontsize.getValue())); - MapImage.setPropertyTerritoryNameAndPuAndCommentColor( - territoryNameColor.getValue()); - MapImage.setPropertyUnitCountColor(unitCountColor.getValue()); - MapImage.setPropertyUnitCountOutline(unitCountOutline.getValue()); - MapImage.setPropertyUnitFactoryDamageColor(factoryDamageColor.getValue()); - MapImage.setPropertyUnitFactoryDamageOutline(factoryDamageOutline.getValue()); - MapImage.setPropertyUnitHitDamageColor(hitDamageColor.getValue()); - MapImage.setPropertyUnitHitDamageOutline(hitDamageOutline.getValue()); - frame.getMapPanel().resetMap(); - } - }); - add(mapFontOptions).setMnemonic(KeyEvent.VK_C); - } - - private void addShowTerritoryEffects() { - final JCheckBoxMenuItem territoryEffectsBox = new JCheckBoxMenuItem("Show TerritoryEffects"); - territoryEffectsBox.setMnemonic(KeyEvent.VK_T); - territoryEffectsBox.addActionListener( - e -> { - uiContext.setShowTerritoryEffects(territoryEffectsBox.isSelected()); - frame.getMapPanel().resetMap(); - }); - add(territoryEffectsBox); - territoryEffectsBox.setSelected(true); - } - - private void addLockMap() { - add( - new JMenuItemCheckBoxBuilder("Lock Map", 'M') - .accelerator(KeyCode.L) - .bindSetting(ClientSetting.lockMap) - .build()); - } - - private void addFlagDisplayModeMenu() { - // 2.0 to 1.9 compatibility hack. Can be removed when all players that have played a 2.0 - // prelease have launched a game containing this patch. When going from 2.0 to 1.9, - // 1.9 will crash due to an enum value not found error when loading 'DRAW_MODE' - final Preferences prefs = Preferences.userNodeForPackage(getClass()); - if (prefs.get("DRAW_MODE", null) != null) { - prefs.remove("DRAW_MODE"); - try { - prefs.flush(); - } catch (final BackingStoreException ignored) { - // ignore - } + + private void addChatTimeMenu() { + if (frame.hasChat()) { + add( + new JMenuItemCheckBoxBuilder("Show Chat Times", 'T') + .bindSetting(ClientSetting.showChatTimeSettings) + .build()); + } } - final JMenu flagDisplayMenu = new JMenu(); - flagDisplayMenu.setMnemonic(KeyEvent.VK_N); - flagDisplayMenu.setText("Flag Display"); - final ButtonGroup flagsDisplayGroup = new ButtonGroup(); - - final JRadioButtonMenuItem noFlags = - new JMenuItemBuilder("Off", KeyCode.O) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.NONE, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - final JRadioButtonMenuItem smallFlags = - new JMenuItemBuilder("Small", KeyCode.S) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - final JRadioButtonMenuItem largeFlags = - new JMenuItemBuilder("Large", KeyCode.L) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - flagDisplayMenu.add(noFlags); - flagDisplayMenu.add(smallFlags); - flagDisplayMenu.add(largeFlags); - - // Add a menu listener to update the checked state of the items, as the flag state - // may change externally (e.g. via UnitScroller UI). - flagDisplayMenu.addMenuListener( - new MenuListener() { - @Override - public void menuSelected(final MenuEvent e) { - final var drawModel = ClientSetting.unitFlagDrawMode.getValueOrThrow(); - noFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.NONE); - smallFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG); - largeFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG); - } - - @Override - public void menuDeselected(final MenuEvent e) {} - - @Override - public void menuCanceled(final MenuEvent e) {} - }); - add(flagDisplayMenu); - } - - private void addChatTimeMenu() { - if (frame.hasChat()) { - add( - new JMenuItemCheckBoxBuilder("Show Chat Times", 'T') - .bindSetting(ClientSetting.showChatTimeSettings) - .build()); + private void addFindTerritory() { + final JMenuItem menuItem = add(new FindTerritoryAction(frame)); + menuItem.setAccelerator( + KeyStroke.getKeyStroke( + KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); + menuItem.setMnemonic(KeyEvent.VK_F); } - } - - private void addFindTerritory() { - final JMenuItem menuItem = add(new FindTerritoryAction(frame)); - menuItem.setAccelerator( - KeyStroke.getKeyStroke( - KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); - menuItem.setMnemonic(KeyEvent.VK_F); - } } From ef4890f3342c738517a4c9595dde251235aaa22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Fedi=C4=8D?= <fedic.patrik@gmail.com> Date: Wed, 15 Nov 2023 09:45:27 +0100 Subject: [PATCH 2/4] better code, structure, logic and formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Fedič <fedic.patrik@gmail.com> --- .../games/strategy/engine/data/GameData.java | 53 +- .../engine/data/events/ZoomMapListener.java | 5 +- .../games/strategy/triplea/ui/BottomBar.java | 101 +- .../triplea/ui/panels/map/MapPanel.java | 12 + .../strategy/triplea/ui/TripleAFrame.java | 1 + .../strategy/triplea/ui/menubar/ViewMenu.java | 928 +++++++++--------- 6 files changed, 549 insertions(+), 551 deletions(-) diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java b/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java index 97a97794c3..7376d4e853 100644 --- a/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/GameData.java @@ -4,7 +4,6 @@ import com.google.common.base.MoreObjects; import games.strategy.engine.data.events.GameDataChangeListener; import games.strategy.engine.data.events.TerritoryListener; -import games.strategy.engine.data.events.ZoomMapListener; import games.strategy.engine.data.properties.GameProperties; import games.strategy.engine.delegate.IDelegate; import games.strategy.engine.framework.GameDataManager; @@ -81,7 +80,7 @@ public class GameData implements Serializable, GameState { @RemoveOnNextMajorRelease @Deprecated private Version gameVersion; private int diceSides; private transient List<TerritoryListener> territoryListeners = new CopyOnWriteArrayList<>(); - private final transient List<ZoomMapListener> zoomMapListeners = new CopyOnWriteArrayList<>(); + private transient List<GameDataChangeListener> dataChangeListeners = new CopyOnWriteArrayList<>(); private transient Map<String, IDelegate> delegates = new HashMap<>(); private final AllianceTracker alliances = new AllianceTracker(); @@ -103,12 +102,12 @@ public class GameData implements Serializable, GameState { private final GameProperties properties = new GameProperties(this); private final UnitsList unitsList = new UnitsList(); private final TechnologyFrontier technologyFrontier = - new TechnologyFrontier("allTechsForGame", this); + new TechnologyFrontier("allTechsForGame", this); @Getter private transient TechTracker techTracker = new TechTracker(this); private final IGameLoader loader = new TripleA(); private History gameHistory = new History(this); private List<Tuple<IAttachment, List<Tuple<String, String>>>> attachmentOrderAndValues = - new ArrayList<>(); + new ArrayList<>(); private final Map<String, TerritoryEffect> territoryEffectList = new HashMap<>(); private final BattleRecordsList battleRecordsList = new BattleRecordsList(this); private transient GameDataEventListeners gameDataEventListeners = new GameDataEventListeners(); @@ -260,10 +259,6 @@ public GameProperties getProperties() { return properties; } - public void addZoomMapListeners(final ZoomMapListener listener) { - zoomMapListeners.add(listener); - } - public void addTerritoryListener(final TerritoryListener listener) { territoryListeners.add(listener); } @@ -290,10 +285,6 @@ void notifyTerritoryUnitsChanged(final Territory t) { territoryListeners.forEach(territoryListener -> territoryListener.unitsChanged(t)); } - public void notifyMapZoomChanged(Integer newZoom) { - zoomMapListeners.forEach(zoomMapListener -> zoomMapListener.zoomMapChanged(newZoom)); - } - void notifyTerritoryAttachmentChanged(final Territory t) { territoryListeners.forEach(territoryListener -> territoryListener.attachmentChanged(t)); } @@ -371,9 +362,9 @@ public void resetHistory() { final boolean oldForceInSwingEventThread = forceInSwingEventThread; forceInSwingEventThread = false; gameHistory - .getHistoryWriter() - .startNextStep( - step.getName(), step.getDelegateName(), step.getPlayerId(), step.getDisplayName()); + .getHistoryWriter() + .startNextStep( + step.getName(), step.getDelegateName(), step.getPlayerId(), step.getDisplayName()); forceInSwingEventThread = oldForceInSwingEventThread; } @@ -456,7 +447,7 @@ private static Unlocker acquireLock(Lock lock) { } public void addToAttachmentOrderAndValues( - final Tuple<IAttachment, List<Tuple<String, String>>> attachmentAndValues) { + final Tuple<IAttachment, List<Tuple<String, String>>> attachmentAndValues) { attachmentOrderAndValues.add(attachmentAndValues); } @@ -465,7 +456,7 @@ public List<Tuple<IAttachment, List<Tuple<String, String>>>> getAttachmentOrderA } public void setAttachmentOrderAndValues( - List<Tuple<IAttachment, List<Tuple<String, String>>>> values) { + List<Tuple<IAttachment, List<Tuple<String, String>>>> values) { attachmentOrderAndValues = values; } @@ -530,12 +521,12 @@ public TechnologyDelegate getTechDelegate() { public void preGameDisablePlayers(final Predicate<GamePlayer> shouldDisablePlayer) { final Set<GamePlayer> playersWhoShouldBeRemoved = new HashSet<>(); playerList.getPlayers().stream() - .filter(p -> (p.getCanBeDisabled() && shouldDisablePlayer.test(p))) - .forEach( - p -> { - p.setIsDisabled(true); - playersWhoShouldBeRemoved.add(p); - }); + .filter(p -> (p.getCanBeDisabled() && shouldDisablePlayer.test(p))) + .forEach( + p -> { + p.setIsDisabled(true); + playersWhoShouldBeRemoved.add(p); + }); if (!playersWhoShouldBeRemoved.isEmpty()) { removePlayerStepsFromSequence(playersWhoShouldBeRemoved); } @@ -574,12 +565,12 @@ public void performChange(final Change change) { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("diceSides", diceSides) - .add("gameName", gameName) - .add("gameVersion", gameVersion) - .add("loader", loader) - .add("playerList", playerList) - .toString(); + .add("diceSides", diceSides) + .add("gameName", gameName) + .add("gameVersion", gameVersion) + .add("loader", loader) + .add("playerList", playerList) + .toString(); } /** @@ -613,11 +604,11 @@ public String loadGameNotes(final Path mapLocation) { public Optional<Path> getGameXmlPath(final Path mapLocation) { // Given a game name, the map.yml file can tell us the path to the game xml file. return findMapDescriptionYaml(mapLocation) - .flatMap(yaml -> yaml.getGameXmlPathByGameName(getGameName())); + .flatMap(yaml -> yaml.getGameXmlPathByGameName(getGameName())); } private Optional<MapDescriptionYaml> findMapDescriptionYaml(final Path mapLocation) { return FileUtils.findFileInParentFolders(mapLocation, MapDescriptionYaml.MAP_YAML_FILE_NAME) - .flatMap(MapDescriptionYaml::fromFile); + .flatMap(MapDescriptionYaml::fromFile); } } diff --git a/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java b/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java index e3482983b3..2ec47b6b88 100644 --- a/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java +++ b/game-app/game-core/src/main/java/games/strategy/engine/data/events/ZoomMapListener.java @@ -1,8 +1,9 @@ package games.strategy.engine.data.events; /** - * A ZoomMapListener will be notified of events that affect a map zoom in ViewMenu in onClick on OK button. - * */ + * A ZoomMapListener will be notified of events that affect a map zoom in ViewMenu in onClick on OK + * button. + */ public interface ZoomMapListener { void zoomMapChanged(Integer newZoom); } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java index b1c90eb3bc..837a214365 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java @@ -18,7 +18,6 @@ import java.awt.Image; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -57,7 +56,7 @@ public class BottomBar extends JPanel implements TerritoryListener, ZoomMapListe private final JLabel playerLabel = new JLabel("xxxxxx"); private final JLabel stepLabel = new JLabel("xxxxxx"); private final JLabel roundLabel = new JLabel("xxxxxx"); - private final JLabel zoomLabel = new JLabel("Zoom: 100%"); + private final JLabel zoomLabel = new JLabel(""); public BottomBar(final UiContext uiContext, final GameData data, final boolean usingDiceServer) { this.uiContext = uiContext; @@ -66,32 +65,30 @@ public BottomBar(final UiContext uiContext, final GameData data, final boolean u setLayout(new BorderLayout()); add(createCenterPanel(), BorderLayout.CENTER); add(createStepPanel(usingDiceServer), BorderLayout.EAST); - - data.addZoomMapListeners(this); } private JPanel createCenterPanel() { final JPanel centerPanel = new JPanel(); centerPanel.setLayout(new GridBagLayout()); final var gridBuilder = - new GridBagConstraintsBuilder().weightY(1).fill(GridBagConstraintsFill.BOTH); + new GridBagConstraintsBuilder().weightY(1).fill(GridBagConstraintsFill.BOTH); centerPanel.add( - resourceBar, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.WEST).build()); + resourceBar, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.WEST).build()); territoryInfo.setPreferredSize(new Dimension(0, 0)); territoryInfo.setBorder(new EtchedBorder(EtchedBorder.RAISED)); centerPanel.add( - territoryInfo, - gridBuilder.gridX(1).weightX(1).anchor(GridBagConstraintsAnchor.CENTER).build()); + territoryInfo, + gridBuilder.gridX(1).weightX(1).anchor(GridBagConstraintsAnchor.CENTER).build()); statusMessage.setVisible(false); statusMessage.setPreferredSize(new Dimension(0, 0)); statusMessage.setBorder(new EtchedBorder(EtchedBorder.RAISED)); centerPanel.add( - statusMessage, gridBuilder.gridX(2).anchor(GridBagConstraintsAnchor.EAST).build()); + statusMessage, gridBuilder.gridX(2).anchor(GridBagConstraintsAnchor.EAST).build()); centerPanel.add( - zoomLabel, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.EAST).build()); + zoomLabel, gridBuilder.weightX(0).anchor(GridBagConstraintsAnchor.EAST).build()); return centerPanel; } @@ -130,10 +127,10 @@ public void setTerritory(final @Nullable Territory territory) { if (territory == null) { SwingUtilities.invokeLater( - () -> { - territoryInfo.removeAll(); - SwingComponents.redraw(territoryInfo); - }); + () -> { + territoryInfo.removeAll(); + SwingComponents.redraw(territoryInfo); + }); return; } @@ -141,9 +138,9 @@ public void setTerritory(final @Nullable Territory territory) { try (GameData.Unlocker ignored = territory.getData().acquireReadLock()) { final String territoryName = territory.getName(); final Collection<UnitCategory> units = - uiContext.isShowUnitsInStatusBar() - ? UnitSeparator.categorize(territory.getUnits()) - : List.of(); + uiContext.isShowUnitsInStatusBar() + ? UnitSeparator.categorize(territory.getUnits()) + : List.of(); final TerritoryAttachment ta = TerritoryAttachment.get(territory); final IntegerMap<Resource> resources = new IntegerMap<>(); final List<String> territoryEffectNames; @@ -151,9 +148,9 @@ public void setTerritory(final @Nullable Territory territory) { territoryEffectNames = List.of(); } else { territoryEffectNames = - ta.getTerritoryEffect().stream() - .map(TerritoryEffect::getName) - .collect(Collectors.toList()); + ta.getTerritoryEffect().stream() + .map(TerritoryEffect::getName) + .collect(Collectors.toList()); final int production = ta.getProduction(); if (production > 0) { resources.add(new Resource(Constants.PUS, territory.getData()), production); @@ -162,15 +159,15 @@ public void setTerritory(final @Nullable Territory territory) { } SwingUtilities.invokeLater( - () -> updateTerritoryInfo(territoryName, territoryEffectNames, units, resources)); + () -> updateTerritoryInfo(territoryName, territoryEffectNames, units, resources)); } } private void updateTerritoryInfo( - String territoryName, - List<String> territoryEffectNames, - Collection<UnitCategory> units, - IntegerMap<Resource> resources) { + String territoryName, + List<String> territoryEffectNames, + Collection<UnitCategory> units, + IntegerMap<Resource> resources) { // Box layout with horizontal glue on both sides achieves the following desirable properties: // 1. If the content is narrower than the available space, it will be centered. // 2. If the content is wider than the available space, then the beginning will be shown, @@ -253,7 +250,7 @@ public void gameDataChanged() { } public void setStepInfo( - int roundNumber, String stepName, @Nullable GamePlayer player, boolean isRemotePlayer) { + int roundNumber, String stepName, @Nullable GamePlayer player, boolean isRemotePlayer) { roundLabel.setText("Round:" + roundNumber + " "); stepLabel.setText(stepName); if (player != null) { @@ -263,11 +260,11 @@ public void setStepInfo( public void setCurrentPlayer(GamePlayer player, boolean isRemotePlayer) { final CompletableFuture<?> future = - CompletableFuture.supplyAsync(() -> uiContext.getFlagImageFactory().getFlag(player)) - .thenApplyAsync(ImageIcon::new) - .thenAccept(icon -> SwingUtilities.invokeLater(() -> roundLabel.setIcon(icon))); + CompletableFuture.supplyAsync(() -> uiContext.getFlagImageFactory().getFlag(player)) + .thenApplyAsync(ImageIcon::new) + .thenAccept(icon -> SwingUtilities.invokeLater(() -> roundLabel.setIcon(icon))); CompletableFutureUtils.logExceptionWhenComplete( - future, throwable -> log.error("Failed to set round icon for " + player, throwable)); + future, throwable -> log.error("Failed to set round icon for " + player, throwable)); playerLabel.setText((isRemotePlayer ? "REMOTE: " : "") + player.getName()); } @@ -275,26 +272,26 @@ private void listenForTerritoryUpdates(@Nullable Territory territory) { // Run async, as this is called while holding a GameData lock so we shouldn't grab a different // data's lock in this case. AsyncRunner.runAsync( - () -> { - GameData oldGameData = currentTerritory != null ? currentTerritory.getData() : null; - GameData newGameData = territory != null ? territory.getData() : null; - // Re-subscribe listener on the right GameData, which could change when toggling - // between history and the current game. - if (!ObjectUtils.referenceEquals(oldGameData, newGameData)) { - if (oldGameData != null) { - try (GameData.Unlocker ignored = oldGameData.acquireWriteLock()) { - oldGameData.removeTerritoryListener(this); - } - } - if (newGameData != null) { - try (GameData.Unlocker ignored = newGameData.acquireWriteLock()) { - newGameData.addTerritoryListener(this); - } - } - } - currentTerritory = territory; - }) - .exceptionally(e -> log.error("Territory listener error:", e)); + () -> { + GameData oldGameData = currentTerritory != null ? currentTerritory.getData() : null; + GameData newGameData = territory != null ? territory.getData() : null; + // Re-subscribe listener on the right GameData, which could change when toggling + // between history and the current game. + if (!ObjectUtils.referenceEquals(oldGameData, newGameData)) { + if (oldGameData != null) { + try (GameData.Unlocker ignored = oldGameData.acquireWriteLock()) { + oldGameData.removeTerritoryListener(this); + } + } + if (newGameData != null) { + try (GameData.Unlocker ignored = newGameData.acquireWriteLock()) { + newGameData.addTerritoryListener(this); + } + } + } + currentTerritory = territory; + }) + .exceptionally(e -> log.error("Territory listener error:", e)); } @Override @@ -312,8 +309,6 @@ public void attachmentChanged(Territory territory) {} @Override public void zoomMapChanged(Integer newZoom) { - if (Objects.nonNull(newZoom)) { - zoomLabel.setText(String.format("Zoom: %d%%", newZoom)); - } + zoomLabel.setText(String.format("Zoom: %d%%", newZoom)); } } diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java index 0afca4bf11..50afb7b559 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/panels/map/MapPanel.java @@ -13,6 +13,7 @@ import games.strategy.engine.data.Unit; import games.strategy.engine.data.events.GameDataChangeListener; import games.strategy.engine.data.events.TerritoryListener; +import games.strategy.engine.data.events.ZoomMapListener; import games.strategy.triplea.Constants; import games.strategy.triplea.delegate.EditDelegate; import games.strategy.triplea.delegate.Matches; @@ -88,6 +89,7 @@ public class MapPanel extends ImageScrollerLargeView { private final List<MapSelectionListener> mapSelectionListeners = new ArrayList<>(); private final List<UnitSelectionListener> unitSelectionListeners = new ArrayList<>(); private final List<MouseOverUnitListener> mouseOverUnitsListeners = new ArrayList<>(); + private final List<ZoomMapListener> zoomMapListeners = new ArrayList<>(); private GameData gameData; // the territory that the mouse is currently over @Getter private @Nullable Territory currentTerritory; @@ -470,6 +472,14 @@ public void setRoute( SwingUtilities.invokeLater(this::repaint); } + public void addZoomMapListener(final ZoomMapListener listener) { + zoomMapListeners.add(listener); + } + + public void removeZoomMapListener(final ZoomMapListener listener) { + zoomMapListeners.remove(listener); + } + public void addMapSelectionListener(final MapSelectionListener listener) { mapSelectionListeners.add(listener); } @@ -841,6 +851,8 @@ public double getScale() { @Override public void setScale(final double newScale) { super.setScale(newScale); + zoomMapListeners.forEach( + (zoomMapListener -> zoomMapListener.zoomMapChanged((int) (scale * 100)))); // setScale will check bounds, and normalize the scale correctly uiContext.setScale(scale); repaint(); diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/TripleAFrame.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/TripleAFrame.java index 40eb389cda..d41bd1813a 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/TripleAFrame.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/TripleAFrame.java @@ -448,6 +448,7 @@ protected void paintComponent(final Graphics g) { data.addGameDataEventListener( GameDataEvent.TECH_ATTACHMENT_CHANGED, this::clearCachedUnitImages); uiContext.addShutdownWindow(this); + mapPanel.addZoomMapListener(bottomBar); } private void clearCachedUnitImages() { diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java index a7144cb103..b7d5e9d03c 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java @@ -52,488 +52,486 @@ @Slf4j final class ViewMenu extends JMenu { - private static final long serialVersionUID = -4703734404422047487L; - - private JCheckBoxMenuItem showMapDetails; - private JCheckBoxMenuItem showMapBlends; - - private final List<Territory> gameMapTerritories; - private final TripleAFrame frame; - private final UiContext uiContext; - - ViewMenu(final TripleAFrame frame) { - super("View"); - - this.frame = frame; - this.uiContext = frame.getUiContext(); - gameMapTerritories = frame.getGame().getData().getMap().getTerritories(); - - setMnemonic(KeyEvent.VK_V); - - addZoomMenu(); - addUnitSizeMenu(); - addLockMap(); - addShowUnitsMenu(); - addShowUnitsInStatusBarMenu(); - addFlagDisplayModeMenu(); - - if (uiContext.getMapData().useTerritoryEffectMarkers()) { - addShowTerritoryEffects(); - } - if (ClientSetting.showBetaFeatures.getValueOrThrow()) { - addMapSkinsMenu(); - } - addShowMapDetails(); - addShowMapBlends(); - addMapFontAndColorEditorMenu(); - addChatTimeMenu(); - addShowCommentLog(); - addSeparator(); - addFindTerritory(); - - showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); - } + private static final long serialVersionUID = -4703734404422047487L; - private void addShowCommentLog() { - add( - new JMenuItemCheckBoxBuilder("Show Comment Log", 'L') - .bindSetting(ClientSetting.showCommentLog) - .actionListener( - value -> { - if (value) { - frame.showCommentLog(); - } else { - frame.hideCommentLog(); - } - }) - .build()); - } + private JCheckBoxMenuItem showMapDetails; + private JCheckBoxMenuItem showMapBlends; - private void addZoomMenu() { - final Action mapZoom = - SwingAction.of( - "Map Zoom", - e -> { - final SpinnerNumberModel model = new SpinnerNumberModel(); - model.setMaximum(UiContext.MAP_SCALE_MAX_VALUE * 100); - model.setMinimum(Math.ceil(frame.getMapPanel().getMinScale() * 100)); - model.setStepSize(1); - model.setValue((double) Math.round(frame.getMapPanel().getScale() * 100)); - final JSpinner spinner = new JSpinner(model); - final JPanel panel = new JPanel(); - panel.setLayout(new BorderLayout()); - panel.add(new JLabel("Choose Map Zoom (%)"), BorderLayout.NORTH); - panel.add(spinner, BorderLayout.CENTER); - final JPanel buttons = new JPanel(); - final JButton fitWidth = new JButton("Fit Width"); - buttons.add(fitWidth); - final JButton fitHeight = new JButton("Fit Height"); - buttons.add(fitHeight); - final JButton reset = new JButton("Reset"); - buttons.add(reset); - panel.add(buttons, BorderLayout.SOUTH); - fitWidth.addActionListener( - event -> { - final double screenWidth = frame.getMapPanel().getWidth(); - final double mapWidth = frame.getMapPanel().getImageWidth(); - double ratio = screenWidth / mapWidth; - ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); - ratio = Math.min(1, ratio); - model.setValue((int) Math.round(ratio * 100)); - }); - fitHeight.addActionListener( - event -> { - final double screenHeight = frame.getMapPanel().getHeight(); - final double mapHeight = frame.getMapPanel().getImageHeight(); - double ratio = screenHeight / mapHeight; - ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); - model.setValue((int) Math.round(ratio * 100)); - }); - reset.addActionListener(event -> model.setValue(100)); - final int result = - JOptionPane.showOptionDialog( - frame, - panel, - "Choose Map Zoom", - JOptionPane.OK_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - new String[] {"OK", "Cancel"}, - 0); - if (result != 0) { - return; - } - final Number value = (Number) model.getValue(); - frame.getMapPanel().setScale(value.doubleValue() / 100); - - frame.getGame().getData().notifyMapZoomChanged(value.intValue()); - }); - add(mapZoom).setMnemonic(KeyEvent.VK_Z); - } + private final List<Territory> gameMapTerritories; + private final TripleAFrame frame; + private final UiContext uiContext; - private void addUnitSizeMenu() { - final NumberFormat decimalFormat = new DecimalFormat("00.##"); - // This is the action listener used - class UnitSizeAction extends AbstractAction { - private static final long serialVersionUID = -6280511505686687867L; - private final double scaleFactor; + ViewMenu(final TripleAFrame frame) { + super("View"); - private UnitSizeAction(final double scaleFactor) { - super(decimalFormat.format(scaleFactor * 100) + "%"); - this.scaleFactor = scaleFactor; - } + this.frame = frame; + this.uiContext = frame.getUiContext(); + gameMapTerritories = frame.getGame().getData().getMap().getTerritories(); - @Override - public void actionPerformed(final ActionEvent e) { - uiContext.setUnitScaleFactor(scaleFactor); - frame.getMapPanel().resetMap(); - } - } - - final JMenu unitSizeMenu = new JMenu(); - unitSizeMenu.setMnemonic(KeyEvent.VK_S); - unitSizeMenu.setText("Unit Size"); - final ButtonGroup unitSizeGroup = new ButtonGroup(); - final JRadioButtonMenuItem radioItem125 = new JRadioButtonMenuItem(new UnitSizeAction(1.25)); - final JRadioButtonMenuItem radioItem100 = new JRadioButtonMenuItem(new UnitSizeAction(1.0)); - radioItem100.setMnemonic(KeyEvent.VK_1); - final JRadioButtonMenuItem radioItem87 = new JRadioButtonMenuItem(new UnitSizeAction(0.875)); - final JRadioButtonMenuItem radioItem83 = new JRadioButtonMenuItem(new UnitSizeAction(0.8333)); - radioItem83.setMnemonic(KeyEvent.VK_8); - final JRadioButtonMenuItem radioItem75 = new JRadioButtonMenuItem(new UnitSizeAction(0.75)); - radioItem75.setMnemonic(KeyEvent.VK_7); - final JRadioButtonMenuItem radioItem66 = new JRadioButtonMenuItem(new UnitSizeAction(0.6666)); - radioItem66.setMnemonic(KeyEvent.VK_6); - final JRadioButtonMenuItem radioItem56 = new JRadioButtonMenuItem(new UnitSizeAction(0.5625)); - final JRadioButtonMenuItem radioItem50 = new JRadioButtonMenuItem(new UnitSizeAction(0.5)); - radioItem50.setMnemonic(KeyEvent.VK_5); - unitSizeGroup.add(radioItem125); - unitSizeGroup.add(radioItem100); - unitSizeGroup.add(radioItem87); - unitSizeGroup.add(radioItem83); - unitSizeGroup.add(radioItem75); - unitSizeGroup.add(radioItem66); - unitSizeGroup.add(radioItem56); - unitSizeGroup.add(radioItem50); - radioItem100.setSelected(true); - // select the closest to the default size - final Enumeration<AbstractButton> enum1 = unitSizeGroup.getElements(); - boolean matchFound = false; - while (enum1.hasMoreElements()) { - final JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) enum1.nextElement(); - final UnitSizeAction action = (UnitSizeAction) menuItem.getAction(); - if (Math.abs(action.scaleFactor - uiContext.getUnitImageFactory().getScaleFactor()) < 0.01) { - menuItem.setSelected(true); - matchFound = true; - break; - } - } - if (!matchFound) { - log.error("default unit size does not match any menu item"); - } - unitSizeMenu.add(radioItem125); - unitSizeMenu.add(radioItem100); - unitSizeMenu.add(radioItem87); - unitSizeMenu.add(radioItem83); - unitSizeMenu.add(radioItem75); - unitSizeMenu.add(radioItem66); - unitSizeMenu.add(radioItem56); - unitSizeMenu.add(radioItem50); - add(unitSizeMenu); - } + setMnemonic(KeyEvent.VK_V); - private void addMapSkinsMenu() { - final JMenu mapSubMenu = new JMenu("Map Skins"); - mapSubMenu.setMnemonic(KeyEvent.VK_K); - add(mapSubMenu); - final ButtonGroup mapButtonGroup = new ButtonGroup(); - final Collection<UiContext.MapSkin> skins = - uiContext.getSkins(frame.getGame().getData().getMapName()); - mapSubMenu.setEnabled(skins.size() > 1); - for (final UiContext.MapSkin mapSkin : skins) { - final JMenuItem mapMenuItem = new JRadioButtonMenuItem(mapSkin.getSkinName()); - mapButtonGroup.add(mapMenuItem); - mapSubMenu.add(mapMenuItem); - mapMenuItem.setSelected(mapSkin.isCurrentSkin()); - mapMenuItem.addActionListener( - e -> { - try { - frame.changeMapSkin(mapSkin.getSkinName()); - if (uiContext.getMapData().getHasRelief()) { - showMapDetails.setSelected(true); - } - showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); - } catch (final Exception exception) { - log.error("Error Changing Map Skin2", exception); - } - }); - } - } + addZoomMenu(); + addUnitSizeMenu(); + addLockMap(); + addShowUnitsMenu(); + addShowUnitsInStatusBarMenu(); + addFlagDisplayModeMenu(); - private void addShowMapDetails() { - showMapDetails = new JCheckBoxMenuItem("Show Map Details"); - showMapDetails.setMnemonic(KeyEvent.VK_D); - showMapDetails.setSelected(TileImageFactory.getShowReliefImages()); - showMapDetails.addActionListener( - e -> { - if (TileImageFactory.getShowReliefImages() == showMapDetails.isSelected()) { - return; - } - TileImageFactory.setShowReliefImages(showMapDetails.isSelected()); - ThreadRunner.runInNewThread( - () -> frame.getMapPanel().updateCountries(gameMapTerritories)); - }); - add(showMapDetails); + if (uiContext.getMapData().useTerritoryEffectMarkers()) { + addShowTerritoryEffects(); } - - private void addShowMapBlends() { - showMapBlends = new JCheckBoxMenuItem("Show Map Blends"); - showMapBlends.setMnemonic(KeyEvent.VK_B); - if (uiContext.getMapData().getHasRelief() - && showMapDetails.isEnabled() - && showMapDetails.isSelected()) { - showMapBlends.setEnabled(true); - showMapBlends.setSelected(TileImageFactory.getShowMapBlends()); - } else { - showMapBlends.setSelected(false); - showMapBlends.setEnabled(false); - } - showMapBlends.addActionListener( - e -> { - if (TileImageFactory.getShowMapBlends() == showMapBlends.isSelected()) { - return; - } - TileImageFactory.setShowMapBlends(showMapBlends.isSelected()); - TileImageFactory.setShowMapBlendMode(uiContext.getMapData().getMapBlendMode()); - TileImageFactory.setShowMapBlendAlpha(uiContext.getMapData().getMapBlendAlpha()); - new Thread( - () -> frame.getMapPanel().updateCountries(gameMapTerritories), - "Show map Blends thread") - .start(); - }); - add(showMapBlends); + if (ClientSetting.showBetaFeatures.getValueOrThrow()) { + addMapSkinsMenu(); } - - private void addShowUnitsMenu() { - final JCheckBoxMenuItem showUnitsBox = new JCheckBoxMenuItem("Show Units"); - showUnitsBox.setMnemonic(KeyEvent.VK_U); - showUnitsBox.setSelected(true); - showUnitsBox.addActionListener( - e -> { - uiContext.setShowUnits(showUnitsBox.isSelected()); - frame.getMapPanel().resetMap(); - }); - add(showUnitsBox); + addShowMapDetails(); + addShowMapBlends(); + addMapFontAndColorEditorMenu(); + addChatTimeMenu(); + addShowCommentLog(); + addSeparator(); + addFindTerritory(); + + showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); + } + + private void addShowCommentLog() { + add( + new JMenuItemCheckBoxBuilder("Show Comment Log", 'L') + .bindSetting(ClientSetting.showCommentLog) + .actionListener( + value -> { + if (value) { + frame.showCommentLog(); + } else { + frame.hideCommentLog(); + } + }) + .build()); + } + + private void addZoomMenu() { + final Action mapZoom = + SwingAction.of( + "Map Zoom", + e -> { + final SpinnerNumberModel model = new SpinnerNumberModel(); + model.setMaximum(UiContext.MAP_SCALE_MAX_VALUE * 100); + model.setMinimum(Math.ceil(frame.getMapPanel().getMinScale() * 100)); + model.setStepSize(1); + model.setValue((double) Math.round(frame.getMapPanel().getScale() * 100)); + final JSpinner spinner = new JSpinner(model); + final JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + panel.add(new JLabel("Choose Map Zoom (%)"), BorderLayout.NORTH); + panel.add(spinner, BorderLayout.CENTER); + final JPanel buttons = new JPanel(); + final JButton fitWidth = new JButton("Fit Width"); + buttons.add(fitWidth); + final JButton fitHeight = new JButton("Fit Height"); + buttons.add(fitHeight); + final JButton reset = new JButton("Reset"); + buttons.add(reset); + panel.add(buttons, BorderLayout.SOUTH); + fitWidth.addActionListener( + event -> { + final double screenWidth = frame.getMapPanel().getWidth(); + final double mapWidth = frame.getMapPanel().getImageWidth(); + double ratio = screenWidth / mapWidth; + ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); + ratio = Math.min(1, ratio); + model.setValue((int) Math.round(ratio * 100)); + }); + fitHeight.addActionListener( + event -> { + final double screenHeight = frame.getMapPanel().getHeight(); + final double mapHeight = frame.getMapPanel().getImageHeight(); + double ratio = screenHeight / mapHeight; + ratio = Math.max(frame.getMapPanel().getMinScale(), ratio); + model.setValue((int) Math.round(ratio * 100)); + }); + reset.addActionListener(event -> model.setValue(100)); + final int result = + JOptionPane.showOptionDialog( + frame, + panel, + "Choose Map Zoom", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + new String[] {"OK", "Cancel"}, + 0); + if (result != 0) { + return; + } + final Number value = (Number) model.getValue(); + frame.getMapPanel().setScale(value.doubleValue() / 100); + }); + add(mapZoom).setMnemonic(KeyEvent.VK_Z); + } + + private void addUnitSizeMenu() { + final NumberFormat decimalFormat = new DecimalFormat("00.##"); + // This is the action listener used + class UnitSizeAction extends AbstractAction { + private static final long serialVersionUID = -6280511505686687867L; + private final double scaleFactor; + + private UnitSizeAction(final double scaleFactor) { + super(decimalFormat.format(scaleFactor * 100) + "%"); + this.scaleFactor = scaleFactor; + } + + @Override + public void actionPerformed(final ActionEvent e) { + uiContext.setUnitScaleFactor(scaleFactor); + frame.getMapPanel().resetMap(); + } } - private void addShowUnitsInStatusBarMenu() { - JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem("Show Units in Status Bar"); - checkbox.setSelected(true); - checkbox.addActionListener( - e -> { - uiContext.setShowUnitsInStatusBar(checkbox.isSelected()); - // Trigger a bottom bar update. - frame.getBottomBar().setTerritory(frame.getMapPanel().getCurrentTerritory()); - }); - add(checkbox); + final JMenu unitSizeMenu = new JMenu(); + unitSizeMenu.setMnemonic(KeyEvent.VK_S); + unitSizeMenu.setText("Unit Size"); + final ButtonGroup unitSizeGroup = new ButtonGroup(); + final JRadioButtonMenuItem radioItem125 = new JRadioButtonMenuItem(new UnitSizeAction(1.25)); + final JRadioButtonMenuItem radioItem100 = new JRadioButtonMenuItem(new UnitSizeAction(1.0)); + radioItem100.setMnemonic(KeyEvent.VK_1); + final JRadioButtonMenuItem radioItem87 = new JRadioButtonMenuItem(new UnitSizeAction(0.875)); + final JRadioButtonMenuItem radioItem83 = new JRadioButtonMenuItem(new UnitSizeAction(0.8333)); + radioItem83.setMnemonic(KeyEvent.VK_8); + final JRadioButtonMenuItem radioItem75 = new JRadioButtonMenuItem(new UnitSizeAction(0.75)); + radioItem75.setMnemonic(KeyEvent.VK_7); + final JRadioButtonMenuItem radioItem66 = new JRadioButtonMenuItem(new UnitSizeAction(0.6666)); + radioItem66.setMnemonic(KeyEvent.VK_6); + final JRadioButtonMenuItem radioItem56 = new JRadioButtonMenuItem(new UnitSizeAction(0.5625)); + final JRadioButtonMenuItem radioItem50 = new JRadioButtonMenuItem(new UnitSizeAction(0.5)); + radioItem50.setMnemonic(KeyEvent.VK_5); + unitSizeGroup.add(radioItem125); + unitSizeGroup.add(radioItem100); + unitSizeGroup.add(radioItem87); + unitSizeGroup.add(radioItem83); + unitSizeGroup.add(radioItem75); + unitSizeGroup.add(radioItem66); + unitSizeGroup.add(radioItem56); + unitSizeGroup.add(radioItem50); + radioItem100.setSelected(true); + // select the closest to the default size + final Enumeration<AbstractButton> enum1 = unitSizeGroup.getElements(); + boolean matchFound = false; + while (enum1.hasMoreElements()) { + final JRadioButtonMenuItem menuItem = (JRadioButtonMenuItem) enum1.nextElement(); + final UnitSizeAction action = (UnitSizeAction) menuItem.getAction(); + if (Math.abs(action.scaleFactor - uiContext.getUnitImageFactory().getScaleFactor()) < 0.01) { + menuItem.setSelected(true); + matchFound = true; + break; + } } - - private void addMapFontAndColorEditorMenu() { - final Action mapFontOptions = - SwingAction.of( - "Map Font and Color", - e -> { - final List<IEditableProperty<?>> properties = new ArrayList<>(); - final NumberProperty fontsize = - new NumberProperty( - "Font Size", null, 60, 0, MapImage.getPropertyMapFont().getSize()); - final ColorProperty territoryNameColor = - new ColorProperty( - "Territory Name and PU Color", - null, - MapImage.getPropertyTerritoryNameAndPuAndCommentColor()); - final ColorProperty unitCountColor = - new ColorProperty("Unit Count Color", null, MapImage.getPropertyUnitCountColor()); - final ColorProperty unitCountOutline = - new ColorProperty( - "Unit Count Outline", null, MapImage.getPropertyUnitCountOutline()); - final ColorProperty factoryDamageColor = - new ColorProperty( - "Factory Damage Color", null, MapImage.getPropertyUnitFactoryDamageColor()); - final ColorProperty factoryDamageOutline = - new ColorProperty( - "Factory Damage Outline", - null, - MapImage.getPropertyUnitFactoryDamageOutline()); - final ColorProperty hitDamageColor = - new ColorProperty( - "Hit Damage Color", null, MapImage.getPropertyUnitHitDamageColor()); - final ColorProperty hitDamageOutline = - new ColorProperty( - "Hit Damage Outline", null, MapImage.getPropertyUnitHitDamageOutline()); - properties.add(fontsize); - properties.add(territoryNameColor); - properties.add(unitCountColor); - properties.add(unitCountOutline); - properties.add(factoryDamageColor); - properties.add(factoryDamageOutline); - properties.add(hitDamageColor); - properties.add(hitDamageOutline); - final PropertiesUi pui = new PropertiesUi(properties, true); - final JPanel ui = new JPanel(); - ui.setLayout(new BorderLayout()); - ui.add(pui, BorderLayout.CENTER); - ui.add( - new JLabel( - "<html>Change the font and color of 'text' (not pictures) on the map. " - + "<br /><em>(Some people encounter problems with the color picker, " - + "and this " - + "<br />is a bug outside of triplea, located in the 'look and feel' " - + "that " - + "<br />you are using. If you have an error come up, try switching to " - + "the " - + "<br />basic 'look and feel', then setting the color, then switching " - + "back.)</em></html>"), - BorderLayout.NORTH); - final Object[] options = {"Set Properties", "Reset To Default", "Cancel"}; - final int result = - JOptionPane.showOptionDialog( - frame, - ui, - "Map Font and Color", - JOptionPane.YES_NO_CANCEL_OPTION, - JOptionPane.PLAIN_MESSAGE, - null, - options, - 2); - if (result == 1) { - MapImage.resetPropertyMapFont(); - MapImage.resetPropertyTerritoryNameAndPuAndCommentColor(); - MapImage.resetPropertyUnitCountColor(); - MapImage.resetPropertyUnitCountOutline(); - MapImage.resetPropertyUnitFactoryDamageColor(); - MapImage.resetPropertyUnitFactoryDamageOutline(); - MapImage.resetPropertyUnitHitDamageColor(); - MapImage.resetPropertyUnitHitDamageOutline(); - frame.getMapPanel().resetMap(); - } else if (result == 0) { - MapImage.setPropertyMapFont(new Font("Arial", Font.BOLD, fontsize.getValue())); - MapImage.setPropertyTerritoryNameAndPuAndCommentColor( - territoryNameColor.getValue()); - MapImage.setPropertyUnitCountColor(unitCountColor.getValue()); - MapImage.setPropertyUnitCountOutline(unitCountOutline.getValue()); - MapImage.setPropertyUnitFactoryDamageColor(factoryDamageColor.getValue()); - MapImage.setPropertyUnitFactoryDamageOutline(factoryDamageOutline.getValue()); - MapImage.setPropertyUnitHitDamageColor(hitDamageColor.getValue()); - MapImage.setPropertyUnitHitDamageOutline(hitDamageOutline.getValue()); - frame.getMapPanel().resetMap(); - } - }); - add(mapFontOptions).setMnemonic(KeyEvent.VK_C); - } - - private void addShowTerritoryEffects() { - final JCheckBoxMenuItem territoryEffectsBox = new JCheckBoxMenuItem("Show TerritoryEffects"); - territoryEffectsBox.setMnemonic(KeyEvent.VK_T); - territoryEffectsBox.addActionListener( - e -> { - uiContext.setShowTerritoryEffects(territoryEffectsBox.isSelected()); - frame.getMapPanel().resetMap(); - }); - add(territoryEffectsBox); - territoryEffectsBox.setSelected(true); - } - - private void addLockMap() { - add( - new JMenuItemCheckBoxBuilder("Lock Map", 'M') - .accelerator(KeyCode.L) - .bindSetting(ClientSetting.lockMap) - .build()); + if (!matchFound) { + log.error("default unit size does not match any menu item"); } - - private void addFlagDisplayModeMenu() { - // 2.0 to 1.9 compatibility hack. Can be removed when all players that have played a 2.0 - // prelease have launched a game containing this patch. When going from 2.0 to 1.9, - // 1.9 will crash due to an enum value not found error when loading 'DRAW_MODE' - final Preferences prefs = Preferences.userNodeForPackage(getClass()); - if (prefs.get("DRAW_MODE", null) != null) { - prefs.remove("DRAW_MODE"); + unitSizeMenu.add(radioItem125); + unitSizeMenu.add(radioItem100); + unitSizeMenu.add(radioItem87); + unitSizeMenu.add(radioItem83); + unitSizeMenu.add(radioItem75); + unitSizeMenu.add(radioItem66); + unitSizeMenu.add(radioItem56); + unitSizeMenu.add(radioItem50); + add(unitSizeMenu); + } + + private void addMapSkinsMenu() { + final JMenu mapSubMenu = new JMenu("Map Skins"); + mapSubMenu.setMnemonic(KeyEvent.VK_K); + add(mapSubMenu); + final ButtonGroup mapButtonGroup = new ButtonGroup(); + final Collection<UiContext.MapSkin> skins = + uiContext.getSkins(frame.getGame().getData().getMapName()); + mapSubMenu.setEnabled(skins.size() > 1); + for (final UiContext.MapSkin mapSkin : skins) { + final JMenuItem mapMenuItem = new JRadioButtonMenuItem(mapSkin.getSkinName()); + mapButtonGroup.add(mapMenuItem); + mapSubMenu.add(mapMenuItem); + mapMenuItem.setSelected(mapSkin.isCurrentSkin()); + mapMenuItem.addActionListener( + e -> { try { - prefs.flush(); - } catch (final BackingStoreException ignored) { - // ignore + frame.changeMapSkin(mapSkin.getSkinName()); + if (uiContext.getMapData().getHasRelief()) { + showMapDetails.setSelected(true); + } + showMapDetails.setEnabled(uiContext.getMapData().getHasRelief()); + } catch (final Exception exception) { + log.error("Error Changing Map Skin2", exception); } - } - - final JMenu flagDisplayMenu = new JMenu(); - flagDisplayMenu.setMnemonic(KeyEvent.VK_N); - flagDisplayMenu.setText("Flag Display"); - final ButtonGroup flagsDisplayGroup = new ButtonGroup(); - - final JRadioButtonMenuItem noFlags = - new JMenuItemBuilder("Off", KeyCode.O) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.NONE, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - final JRadioButtonMenuItem smallFlags = - new JMenuItemBuilder("Small", KeyCode.S) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - final JRadioButtonMenuItem largeFlags = - new JMenuItemBuilder("Large", KeyCode.L) - .actionListener( - () -> - FlagDrawMode.toggleDrawMode( - UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG, frame.getMapPanel())) - .buildRadio(flagsDisplayGroup); - - flagDisplayMenu.add(noFlags); - flagDisplayMenu.add(smallFlags); - flagDisplayMenu.add(largeFlags); - - // Add a menu listener to update the checked state of the items, as the flag state - // may change externally (e.g. via UnitScroller UI). - flagDisplayMenu.addMenuListener( - new MenuListener() { - @Override - public void menuSelected(final MenuEvent e) { - final var drawModel = ClientSetting.unitFlagDrawMode.getValueOrThrow(); - noFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.NONE); - smallFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG); - largeFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG); - } - - @Override - public void menuDeselected(final MenuEvent e) {} - - @Override - public void menuCanceled(final MenuEvent e) {} - }); - add(flagDisplayMenu); + }); } - - private void addChatTimeMenu() { - if (frame.hasChat()) { - add( - new JMenuItemCheckBoxBuilder("Show Chat Times", 'T') - .bindSetting(ClientSetting.showChatTimeSettings) - .build()); - } + } + + private void addShowMapDetails() { + showMapDetails = new JCheckBoxMenuItem("Show Map Details"); + showMapDetails.setMnemonic(KeyEvent.VK_D); + showMapDetails.setSelected(TileImageFactory.getShowReliefImages()); + showMapDetails.addActionListener( + e -> { + if (TileImageFactory.getShowReliefImages() == showMapDetails.isSelected()) { + return; + } + TileImageFactory.setShowReliefImages(showMapDetails.isSelected()); + ThreadRunner.runInNewThread( + () -> frame.getMapPanel().updateCountries(gameMapTerritories)); + }); + add(showMapDetails); + } + + private void addShowMapBlends() { + showMapBlends = new JCheckBoxMenuItem("Show Map Blends"); + showMapBlends.setMnemonic(KeyEvent.VK_B); + if (uiContext.getMapData().getHasRelief() + && showMapDetails.isEnabled() + && showMapDetails.isSelected()) { + showMapBlends.setEnabled(true); + showMapBlends.setSelected(TileImageFactory.getShowMapBlends()); + } else { + showMapBlends.setSelected(false); + showMapBlends.setEnabled(false); + } + showMapBlends.addActionListener( + e -> { + if (TileImageFactory.getShowMapBlends() == showMapBlends.isSelected()) { + return; + } + TileImageFactory.setShowMapBlends(showMapBlends.isSelected()); + TileImageFactory.setShowMapBlendMode(uiContext.getMapData().getMapBlendMode()); + TileImageFactory.setShowMapBlendAlpha(uiContext.getMapData().getMapBlendAlpha()); + new Thread( + () -> frame.getMapPanel().updateCountries(gameMapTerritories), + "Show map Blends thread") + .start(); + }); + add(showMapBlends); + } + + private void addShowUnitsMenu() { + final JCheckBoxMenuItem showUnitsBox = new JCheckBoxMenuItem("Show Units"); + showUnitsBox.setMnemonic(KeyEvent.VK_U); + showUnitsBox.setSelected(true); + showUnitsBox.addActionListener( + e -> { + uiContext.setShowUnits(showUnitsBox.isSelected()); + frame.getMapPanel().resetMap(); + }); + add(showUnitsBox); + } + + private void addShowUnitsInStatusBarMenu() { + JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem("Show Units in Status Bar"); + checkbox.setSelected(true); + checkbox.addActionListener( + e -> { + uiContext.setShowUnitsInStatusBar(checkbox.isSelected()); + // Trigger a bottom bar update. + frame.getBottomBar().setTerritory(frame.getMapPanel().getCurrentTerritory()); + }); + add(checkbox); + } + + private void addMapFontAndColorEditorMenu() { + final Action mapFontOptions = + SwingAction.of( + "Map Font and Color", + e -> { + final List<IEditableProperty<?>> properties = new ArrayList<>(); + final NumberProperty fontsize = + new NumberProperty( + "Font Size", null, 60, 0, MapImage.getPropertyMapFont().getSize()); + final ColorProperty territoryNameColor = + new ColorProperty( + "Territory Name and PU Color", + null, + MapImage.getPropertyTerritoryNameAndPuAndCommentColor()); + final ColorProperty unitCountColor = + new ColorProperty("Unit Count Color", null, MapImage.getPropertyUnitCountColor()); + final ColorProperty unitCountOutline = + new ColorProperty( + "Unit Count Outline", null, MapImage.getPropertyUnitCountOutline()); + final ColorProperty factoryDamageColor = + new ColorProperty( + "Factory Damage Color", null, MapImage.getPropertyUnitFactoryDamageColor()); + final ColorProperty factoryDamageOutline = + new ColorProperty( + "Factory Damage Outline", + null, + MapImage.getPropertyUnitFactoryDamageOutline()); + final ColorProperty hitDamageColor = + new ColorProperty( + "Hit Damage Color", null, MapImage.getPropertyUnitHitDamageColor()); + final ColorProperty hitDamageOutline = + new ColorProperty( + "Hit Damage Outline", null, MapImage.getPropertyUnitHitDamageOutline()); + properties.add(fontsize); + properties.add(territoryNameColor); + properties.add(unitCountColor); + properties.add(unitCountOutline); + properties.add(factoryDamageColor); + properties.add(factoryDamageOutline); + properties.add(hitDamageColor); + properties.add(hitDamageOutline); + final PropertiesUi pui = new PropertiesUi(properties, true); + final JPanel ui = new JPanel(); + ui.setLayout(new BorderLayout()); + ui.add(pui, BorderLayout.CENTER); + ui.add( + new JLabel( + "<html>Change the font and color of 'text' (not pictures) on the map. " + + "<br /><em>(Some people encounter problems with the color picker, " + + "and this " + + "<br />is a bug outside of triplea, located in the 'look and feel' " + + "that " + + "<br />you are using. If you have an error come up, try switching to " + + "the " + + "<br />basic 'look and feel', then setting the color, then switching " + + "back.)</em></html>"), + BorderLayout.NORTH); + final Object[] options = {"Set Properties", "Reset To Default", "Cancel"}; + final int result = + JOptionPane.showOptionDialog( + frame, + ui, + "Map Font and Color", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.PLAIN_MESSAGE, + null, + options, + 2); + if (result == 1) { + MapImage.resetPropertyMapFont(); + MapImage.resetPropertyTerritoryNameAndPuAndCommentColor(); + MapImage.resetPropertyUnitCountColor(); + MapImage.resetPropertyUnitCountOutline(); + MapImage.resetPropertyUnitFactoryDamageColor(); + MapImage.resetPropertyUnitFactoryDamageOutline(); + MapImage.resetPropertyUnitHitDamageColor(); + MapImage.resetPropertyUnitHitDamageOutline(); + frame.getMapPanel().resetMap(); + } else if (result == 0) { + MapImage.setPropertyMapFont(new Font("Arial", Font.BOLD, fontsize.getValue())); + MapImage.setPropertyTerritoryNameAndPuAndCommentColor( + territoryNameColor.getValue()); + MapImage.setPropertyUnitCountColor(unitCountColor.getValue()); + MapImage.setPropertyUnitCountOutline(unitCountOutline.getValue()); + MapImage.setPropertyUnitFactoryDamageColor(factoryDamageColor.getValue()); + MapImage.setPropertyUnitFactoryDamageOutline(factoryDamageOutline.getValue()); + MapImage.setPropertyUnitHitDamageColor(hitDamageColor.getValue()); + MapImage.setPropertyUnitHitDamageOutline(hitDamageOutline.getValue()); + frame.getMapPanel().resetMap(); + } + }); + add(mapFontOptions).setMnemonic(KeyEvent.VK_C); + } + + private void addShowTerritoryEffects() { + final JCheckBoxMenuItem territoryEffectsBox = new JCheckBoxMenuItem("Show TerritoryEffects"); + territoryEffectsBox.setMnemonic(KeyEvent.VK_T); + territoryEffectsBox.addActionListener( + e -> { + uiContext.setShowTerritoryEffects(territoryEffectsBox.isSelected()); + frame.getMapPanel().resetMap(); + }); + add(territoryEffectsBox); + territoryEffectsBox.setSelected(true); + } + + private void addLockMap() { + add( + new JMenuItemCheckBoxBuilder("Lock Map", 'M') + .accelerator(KeyCode.L) + .bindSetting(ClientSetting.lockMap) + .build()); + } + + private void addFlagDisplayModeMenu() { + // 2.0 to 1.9 compatibility hack. Can be removed when all players that have played a 2.0 + // prelease have launched a game containing this patch. When going from 2.0 to 1.9, + // 1.9 will crash due to an enum value not found error when loading 'DRAW_MODE' + final Preferences prefs = Preferences.userNodeForPackage(getClass()); + if (prefs.get("DRAW_MODE", null) != null) { + prefs.remove("DRAW_MODE"); + try { + prefs.flush(); + } catch (final BackingStoreException ignored) { + // ignore + } } - private void addFindTerritory() { - final JMenuItem menuItem = add(new FindTerritoryAction(frame)); - menuItem.setAccelerator( - KeyStroke.getKeyStroke( - KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); - menuItem.setMnemonic(KeyEvent.VK_F); + final JMenu flagDisplayMenu = new JMenu(); + flagDisplayMenu.setMnemonic(KeyEvent.VK_N); + flagDisplayMenu.setText("Flag Display"); + final ButtonGroup flagsDisplayGroup = new ButtonGroup(); + + final JRadioButtonMenuItem noFlags = + new JMenuItemBuilder("Off", KeyCode.O) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.NONE, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + final JRadioButtonMenuItem smallFlags = + new JMenuItemBuilder("Small", KeyCode.S) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + final JRadioButtonMenuItem largeFlags = + new JMenuItemBuilder("Large", KeyCode.L) + .actionListener( + () -> + FlagDrawMode.toggleDrawMode( + UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG, frame.getMapPanel())) + .buildRadio(flagsDisplayGroup); + + flagDisplayMenu.add(noFlags); + flagDisplayMenu.add(smallFlags); + flagDisplayMenu.add(largeFlags); + + // Add a menu listener to update the checked state of the items, as the flag state + // may change externally (e.g. via UnitScroller UI). + flagDisplayMenu.addMenuListener( + new MenuListener() { + @Override + public void menuSelected(final MenuEvent e) { + final var drawModel = ClientSetting.unitFlagDrawMode.getValueOrThrow(); + noFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.NONE); + smallFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.SMALL_FLAG); + largeFlags.setSelected(drawModel == UnitsDrawer.UnitFlagDrawMode.LARGE_FLAG); + } + + @Override + public void menuDeselected(final MenuEvent e) {} + + @Override + public void menuCanceled(final MenuEvent e) {} + }); + add(flagDisplayMenu); + } + + private void addChatTimeMenu() { + if (frame.hasChat()) { + add( + new JMenuItemCheckBoxBuilder("Show Chat Times", 'T') + .bindSetting(ClientSetting.showChatTimeSettings) + .build()); } + } + + private void addFindTerritory() { + final JMenuItem menuItem = add(new FindTerritoryAction(frame)); + menuItem.setAccelerator( + KeyStroke.getKeyStroke( + KeyEvent.VK_F, Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx())); + menuItem.setMnemonic(KeyEvent.VK_F); + } } From 56f2d43338c7f7d48139e333b13d5057b47ab5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Fedi=C4=8D?= <fedic.patrik@gmail.com> Date: Wed, 22 Nov 2023 13:40:45 +0100 Subject: [PATCH 3/4] show map zoom option and better UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Fedič <fedic.patrik@gmail.com> --- .../java/games/strategy/triplea/ui/BottomBar.java | 8 ++++++++ .../games/strategy/triplea/ui/menubar/ViewMenu.java | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java index 837a214365..6c1d822f14 100644 --- a/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java +++ b/game-app/game-core/src/main/java/games/strategy/triplea/ui/BottomBar.java @@ -85,6 +85,10 @@ private JPanel createCenterPanel() { statusMessage.setVisible(false); statusMessage.setPreferredSize(new Dimension(0, 0)); statusMessage.setBorder(new EtchedBorder(EtchedBorder.RAISED)); + + zoomLabel.setVisible(false); + zoomLabel.setBorder(new EtchedBorder(EtchedBorder.RAISED)); + centerPanel.add( statusMessage, gridBuilder.gridX(2).anchor(GridBagConstraintsAnchor.EAST).build()); centerPanel.add( @@ -268,6 +272,10 @@ public void setCurrentPlayer(GamePlayer player, boolean isRemotePlayer) { playerLabel.setText((isRemotePlayer ? "REMOTE: " : "") + player.getName()); } + public void setMapZoomEnabled(boolean enabled) { + zoomLabel.setVisible(enabled); + } + private void listenForTerritoryUpdates(@Nullable Territory territory) { // Run async, as this is called while holding a GameData lock so we shouldn't grab a different // data's lock in this case. diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java index b7d5e9d03c..44271dd859 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java @@ -85,6 +85,7 @@ final class ViewMenu extends JMenu { } addShowMapDetails(); addShowMapBlends(); + addShowZoomMenu(); addMapFontAndColorEditorMenu(); addChatTimeMenu(); addShowCommentLog(); @@ -324,6 +325,17 @@ private void addShowUnitsMenu() { add(showUnitsBox); } + private void addShowZoomMenu() { + final JCheckBoxMenuItem showMapZoomBox = new JCheckBoxMenuItem("Show Map Zoom"); + + showMapZoomBox.addActionListener( + e -> { + this.frame.getBottomBar().setMapZoomEnabled(showMapZoomBox.isSelected()); + }); + + add(showMapZoomBox); + } + private void addShowUnitsInStatusBarMenu() { JCheckBoxMenuItem checkbox = new JCheckBoxMenuItem("Show Units in Status Bar"); checkbox.setSelected(true); From ac213cab15bf001d3d6c6f75ba3d7aa439a36f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Fedi=C4=8D?= <fedic.patrik@gmail.com> Date: Wed, 22 Nov 2023 13:49:26 +0100 Subject: [PATCH 4/4] option text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Patrik Fedič <fedic.patrik@gmail.com> --- .../main/java/games/strategy/triplea/ui/menubar/ViewMenu.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java index 44271dd859..5a0221ffe0 100644 --- a/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java +++ b/game-app/game-headed/src/main/java/games/strategy/triplea/ui/menubar/ViewMenu.java @@ -326,7 +326,7 @@ private void addShowUnitsMenu() { } private void addShowZoomMenu() { - final JCheckBoxMenuItem showMapZoomBox = new JCheckBoxMenuItem("Show Map Zoom"); + final JCheckBoxMenuItem showMapZoomBox = new JCheckBoxMenuItem("Show Zoom Percentage"); showMapZoomBox.addActionListener( e -> {