package games.strategy.triplea.ui.menubar; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.net.URL; import java.util.List; import java.util.Map; import java.util.Optional; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JEditorPane; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import com.apple.eawt.Application; import games.strategy.engine.ClientContext; import games.strategy.engine.data.GameData; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.ResourceCollection; import games.strategy.engine.data.UnitType; import games.strategy.triplea.UrlConstants; import games.strategy.triplea.delegate.BattleCalculator; import games.strategy.triplea.image.UnitImageFactory; import games.strategy.triplea.ui.IUIContext; import games.strategy.ui.SwingAction; import games.strategy.ui.SwingComponents; import games.strategy.util.LocalizeHTML; public class HelpMenu { private final IUIContext iuiContext; private final GameData gameData; public HelpMenu(final JMenuBar menuBar, final IUIContext iuiContext, final GameData gameData, final Color backgroundColor) { this.iuiContext = iuiContext; this.gameData = gameData; final JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic(KeyEvent.VK_H); menuBar.add(helpMenu); addMoveHelpMenu(helpMenu); addUnitHelpMenu(helpMenu); addGameNotesMenu(helpMenu); addAboutMenu(helpMenu, backgroundColor); addReportBugsMenu(helpMenu); } private static void addMoveHelpMenu(final JMenu parentMenu) { final String moveSelectionHelpTitle = "Movement/Selection Help"; parentMenu.add(SwingAction.of(moveSelectionHelpTitle, e -> { // html formatted string final String hints = "<b> Selecting Units</b><br>" + "Left click on a unit stack to select 1 unit.<br>" + "ALT-Left click on a unit stack to select 10 units of that type in the stack.<br>" + "CTRL-Left click on a unit stack to select all units of that type in the stack.<br>" + "Shift-Left click on a unit to select all units in the territory.<br>" + "Left click on a territory but not on a unit to bring up a selection window for inputing the desired " + "selection.<br>" + "<br><b> Deselecting Units</b><br>" + "Right click somewhere not on a unit stack to unselect the last selected unit.<br>" + "Right click on a unit stack to unselect one unit in the stack.<br>" + "ALT-Right click on a unit stack to unselect 10 units of that type in the stack.<br>" + "CTRL-Right click on a unit stack to unselect all units of that type in the stack.<br>" + "CTRL-Right click somewhere not on a unit stack to unselect all units selected.<br>" + "<br><b> Moving Units</b><br>" + "After selecting units Left click on a territory to move units there (do not Left click and Drag, instead " + "select units, then move the mouse, then select the territory).<br>" + "CTRL-Left click on a territory to select the territory as a way point (this will force the units to move " + "through this territory on their way to the destination).<br>" + "<br><b> Moving the Map Screen</b><br>" + "Right click and Drag the mouse to move your screen over the map.<br>" + "Left click the map (anywhere), use the arrow keys (or WASD keys) to move your map around. Holding down " + "control will move the map faster.<br />" + "Left click in the Minimap at the top right of the screen, and Drag the mouse.<br>" + "Move the mouse to the edge of the map to scroll in that direction. Moving the mouse even closer to the " + "edge will scroll faster.<br>" + "Scrolling the mouse wheel will move the map up and down.<br>" + "<br><b> Zooming Out</b><br>" + "Holding ALT while Scrolling the Mouse Wheel will zoom the map in and out.<br>" + "Select 'Zoom' from the 'View' menu, and change to the desired level.<br>" + "<br><b> Turn off Map Artwork</b><br>" + "Deselect 'Map Details' in the 'View' menu, to show a map without the artwork.<br>" + "Select a new 'Map Skin' from the 'View' menu to show a different kind of artwork (not all maps have " + "skins).<br>" + "<br><b> Other Things</b><br>" + "Press 'n' to cycle through units with movement left (move phases only).<br>" + "Press 'f' to highlight all units you own that have movement left (move phases only).<br>" + "Press 'i' or 'v' to popup info on whatever territory and unit your mouse is currently over.<br>" + "Press 'u' while mousing over a unit to undo all moves that unit has made (beta).<br>" + "To list specific units from a territory in the Territory panel, drag and drop from the territory on the " + "map to the territory panel.<br>"; final JEditorPane editorPane = new JEditorPane(); editorPane.setEditable(false); editorPane.setContentType("text/html"); editorPane.setText(hints); final JScrollPane scroll = new JScrollPane(editorPane); JOptionPane.showMessageDialog(null, scroll, moveSelectionHelpTitle, JOptionPane.PLAIN_MESSAGE); })).setMnemonic(KeyEvent.VK_M); } static String getUnitStatsTable(final GameData gameData, final IUIContext iuiContext) { // html formatted string int i = 0; final String color1 = "ABABAB"; final String color2 = "BDBDBD"; final String color3 = "FEECE2"; final StringBuilder hints = new StringBuilder(); hints.append("<html>"); try { gameData.acquireReadLock(); final Map<PlayerID, Map<UnitType, ResourceCollection>> costs = BattleCalculator.getResourceCostsForTUV(gameData, true); final Map<PlayerID, List<UnitType>> playerUnitTypes = UnitType.getAllPlayerUnitsWithImages(gameData, iuiContext, true); for (final Map.Entry<PlayerID, List<UnitType>> entry : playerUnitTypes.entrySet()) { final PlayerID player = entry.getKey(); hints.append("<p><table border=\"1\" bgcolor=\"" + color1 + "\">"); hints.append("<tr><th style=\"font-size:120%;000000\" bgcolor=\"" + color3 + "\" colspan=\"4\">") .append(player == null ? "NULL" : player.getName()).append(" Units</th></tr>"); hints.append("<tr").append(((i & 1) == 0) ? " bgcolor=\"" + color1 + "\"" : " bgcolor=\"" + color2 + "\"") .append("><td>Unit</td><td>Name</td><td>Cost</td><td>Tool Tip</td></tr>"); for (final UnitType ut : entry.getValue()) { i++; hints.append("<tr").append(((i & 1) == 0) ? " bgcolor=\"" + color1 + "\"" : " bgcolor=\"" + color2 + "\"") .append(">").append("<td>").append(getUnitImageURL(ut, player, iuiContext)).append("</td>").append("<td>") .append(ut.getName()).append("</td>").append("<td>").append(costs.get(player).get(ut).toStringForHTML()) .append("</td>").append("<td>").append(ut.getTooltip(player)).append("</td></tr>"); } i++; hints.append("<tr").append(((i & 1) == 0) ? " bgcolor=\"" + color1 + "\"" : " bgcolor=\"" + color2 + "\"") .append(">").append("<td>Unit</td><td>Name</td><td>Cost</td><td>Tool Tip</td></tr></table></p><br />"); } } finally { gameData.releaseReadLock(); } hints.append("</html>"); return hints.toString(); } private static String getUnitImageURL(final UnitType unitType, final PlayerID player, final IUIContext iuiContext) { final UnitImageFactory unitImageFactory = iuiContext.getUnitImageFactory(); if (player == null || unitImageFactory == null) { return "no image"; } final Optional<URL> imageUrl = unitImageFactory.getBaseImageURL(unitType.getName(), player); final String imageLocation = imageUrl.isPresent() ? imageUrl.get().toString() : ""; return "<img src=\"" + imageLocation + "\" border=\"0\"/>"; } private void addUnitHelpMenu(final JMenu parentMenu) { final String unitHelpTitle = "Unit Help"; parentMenu.add(SwingAction.of(unitHelpTitle, e -> { final JEditorPane editorPane = new JEditorPane(); editorPane.setEditable(false); editorPane.setContentType("text/html"); editorPane.setText(getUnitStatsTable(gameData, iuiContext)); editorPane.setCaretPosition(0); final JScrollPane scroll = new JScrollPane(editorPane); scroll.setBorder(BorderFactory.createEmptyBorder()); final Dimension screenResolution = Toolkit.getDefaultToolkit().getScreenSize(); // not only do we have a start bar, but we also have the message dialog to account for just the scroll bars plus // the window sides final int availHeight = screenResolution.height - 120; final int availWidth = screenResolution.width - 40; scroll .setPreferredSize(new Dimension( (scroll.getPreferredSize().width > availWidth ? availWidth : (scroll.getPreferredSize().height > availHeight ? Math.min(availWidth, scroll.getPreferredSize().width + 22) : scroll.getPreferredSize().width)), (scroll.getPreferredSize().height > availHeight ? availHeight : (scroll.getPreferredSize().width > availWidth ? Math.min(availHeight, scroll.getPreferredSize().height + 22) : scroll.getPreferredSize().height)))); final JDialog dialog = SwingComponents.newJDialog(unitHelpTitle); dialog.add(scroll, BorderLayout.CENTER); final JPanel buttons = new JPanel(); final JButton button = new JButton(SwingAction.of("OK", event -> { dialog.setVisible(false); dialog.removeAll(); dialog.dispose(); })); buttons.add(button); dialog.getRootPane().setDefaultButton(button); dialog.add(buttons, BorderLayout.SOUTH); dialog.pack(); // dialog.setLocationRelativeTo(frame); dialog.addWindowListener(new WindowAdapter() { @Override public void windowOpened(final WindowEvent e) { scroll.getVerticalScrollBar().getModel().setValue(0); scroll.getHorizontalScrollBar().getModel().setValue(0); button.requestFocus(); } }); dialog.setVisible(true); // dialog.dispose(); })).setMnemonic(KeyEvent.VK_U); } public static final JEditorPane gameNotesPane = new JEditorPane(); private void addGameNotesMenu(final JMenu parentMenu) { // allow the game developer to write notes that appear in the game // displays whatever is in the notes field in html final String notesProperty = gameData.getProperties().get("notes", ""); if (notesProperty != null && notesProperty.trim().length() != 0) { final String notes = LocalizeHTML.localizeImgLinksInHTML(notesProperty.trim()); gameNotesPane.setEditable(false); gameNotesPane.setContentType("text/html"); gameNotesPane.setText(notes); final String gameNotesTitle = "Game Notes"; parentMenu.add(SwingAction.of(gameNotesTitle, e -> SwingUtilities.invokeLater(() -> { final JScrollPane scroll = new JScrollPane(gameNotesPane); scroll.scrollRectToVisible(new Rectangle(0, 0, 0, 0)); final JDialog dialog = SwingComponents.newJDialog(gameNotesTitle); dialog.add(scroll, BorderLayout.CENTER); final JPanel buttons = new JPanel(); final JButton button = new JButton(SwingAction.of("OK", event -> { dialog.setVisible(false); dialog.removeAll(); dialog.dispose(); })); buttons.add(button); dialog.getRootPane().setDefaultButton(button); dialog.add(buttons, BorderLayout.SOUTH); dialog.pack(); if (dialog.getWidth() < 400) { dialog.setSize(400, dialog.getHeight()); } if (dialog.getHeight() < 300) { dialog.setSize(dialog.getWidth(), 300); } if (dialog.getWidth() > 800) { dialog.setSize(800, dialog.getHeight()); } if (dialog.getHeight() > 600) { dialog.setSize(dialog.getWidth(), 600); } // dialog.setLocationRelativeTo(frame); dialog.addWindowListener(new WindowAdapter() { @Override public void windowOpened(final WindowEvent e) { scroll.getVerticalScrollBar().getModel().setValue(0); scroll.getHorizontalScrollBar().getModel().setValue(0); button.requestFocus(); } }); dialog.setVisible(true); }))).setMnemonic(KeyEvent.VK_N); } } private void addAboutMenu(final JMenu parentMenu, final Color backgroundColor) { final String text = "<h2>" + gameData.getGameName() + "</h2>" + "<p><b>Engine Version:</b> " + ClientContext.engineVersion() + "<br><b>Game:</b> " + gameData.getGameName() + "<br><b>Game Version:</b> " + gameData.getGameVersion() + "</p>" + "<p>For more information please visit,<br><br>" + "<b><a hlink='" + UrlConstants.TRIPLEA_WEBSITE + "'>" + UrlConstants.TRIPLEA_WEBSITE + "</a></b><br><br>"; final JEditorPane editorPane = new JEditorPane(); editorPane.setBorder(null); editorPane.setBackground(backgroundColor); editorPane.setEditable(false); editorPane.setContentType("text/html"); editorPane.setText(text); final JScrollPane scroll = new JScrollPane(editorPane); scroll.setBorder(null); if (System.getProperty("mrj.version") == null) { parentMenu.addSeparator(); parentMenu.add(SwingAction.of("About", e -> JOptionPane.showMessageDialog(null, editorPane, "About " + gameData.getGameName(), JOptionPane.PLAIN_MESSAGE))).setMnemonic(KeyEvent.VK_A); } else { // On Mac OS X, put the About menu where Mac users expect it to be Application.getApplication().setAboutHandler(paramAboutEvent -> JOptionPane.showMessageDialog(null, editorPane, "About " + gameData.getGameName(), JOptionPane.PLAIN_MESSAGE)); } } private static void addReportBugsMenu(final JMenu parentMenu) { parentMenu.add(SwingAction.of("Send Bug Report", e -> SwingComponents.newOpenUrlConfirmationDialog(UrlConstants.GITHUB_ISSUES))).setMnemonic(KeyEvent.VK_B); } }