package games.strategy.engine.framework.startup.ui; import java.awt.BorderLayout; import java.awt.Frame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.prefs.Preferences; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRadioButton; import javax.swing.JTextField; import javax.swing.border.EmptyBorder; import games.strategy.debug.ErrorConsole; import games.strategy.engine.data.properties.IEditableProperty; import games.strategy.engine.data.properties.NumberProperty; import games.strategy.engine.data.properties.PropertiesUI; import games.strategy.engine.framework.GameRunner; import games.strategy.engine.framework.ProcessRunnerUtil; import games.strategy.engine.framework.lookandfeel.LookAndFeel; import games.strategy.engine.framework.system.HttpProxy; import games.strategy.engine.framework.system.Memory; import games.strategy.sound.SoundOptions; import games.strategy.triplea.settings.SettingsWindow; import games.strategy.triplea.ui.menubar.TripleAMenuBar; import games.strategy.ui.IntTextField; import games.strategy.ui.SwingAction; import games.strategy.ui.SwingComponents; import games.strategy.util.CountDownLatchHandler; import games.strategy.util.EventThreadJOptionPane; import games.strategy.util.Triple; import tools.map.making.MapCreator; import tools.map.xml.creator.MapXmlCreator; /** * Class for holding various engine related options and preferences. */ public class EnginePreferences extends JDialog { private static final long serialVersionUID = 5071190543005064757L; private final Frame m_parentFrame; private JButton m_okButton; private JButton m_lookAndFeel; private JButton m_gameParser; private JButton m_setupProxies; private JButton m_hostWaitTime; private JButton m_setMaxMemory; private JButton m_console; // private JButton m_runAutoHost; private JButton m_mapCreator; private JButton m_mapXmlCreator; private EnginePreferences(final Frame parentFrame) { super(parentFrame, "Edit TripleA Engine Preferences", true); this.m_parentFrame = parentFrame; createComponents(); layoutCoponents(); setupListeners(); setWidgetActivation(); // Listen for windowOpened event to set focus this.addWindowListener(new WindowAdapter() { @Override public void windowOpened(final WindowEvent e) { m_okButton.requestFocus(); } }); } private void createComponents() { m_okButton = new JButton("OK"); m_lookAndFeel = new JButton("Set Look And Feel"); m_gameParser = new JButton("Enable/Disable Delayed Parsing of Game XML's"); m_setupProxies = new JButton("Setup Network and Proxy Settings"); m_hostWaitTime = new JButton("Set Max Host Wait Time for Clients and Observers"); m_setMaxMemory = new JButton("Set Max Memory Usage"); m_mapCreator = new JButton("Run the Map Creator"); m_mapXmlCreator = new JButton("[Beta] Run the Map Creator"); m_console = new JButton("Show Console"); } private void layoutCoponents() { setLayout(new BorderLayout()); final JPanel buttonsPanel = new JPanel(); add(buttonsPanel, BorderLayout.CENTER); buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS)); buttonsPanel.add(Box.createGlue()); // add buttons here: SoundOptions.addGlobalSoundSwitchCheckbox(buttonsPanel); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(SwingComponents.newJButton("Engine Settings", e -> SettingsWindow.showWindow())); buttonsPanel.add(new JLabel(" ")); SoundOptions.addToPanel(buttonsPanel); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_lookAndFeel); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_gameParser); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_setupProxies); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_hostWaitTime); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_setMaxMemory); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_mapCreator); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_mapXmlCreator); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(m_console); buttonsPanel.add(new JLabel(" ")); buttonsPanel.add(Box.createGlue()); buttonsPanel.setBorder(new EmptyBorder(20, 20, 20, 20)); final JPanel main = new JPanel(); main.setBorder(new EmptyBorder(30, 30, 30, 30)); main.setLayout(new BoxLayout(main, BoxLayout.X_AXIS)); main.add(m_okButton); add(main, BorderLayout.SOUTH); } private void setupListeners() { m_okButton.addActionListener(SwingAction.of("OK", e -> setVisible(false))); final String lookAndFeelTitle = "Set Look And Feel"; m_lookAndFeel.addActionListener(SwingAction.of(lookAndFeelTitle, e -> { final Triple<JList<String>, Map<String, String>, String> lookAndFeel = TripleAMenuBar.getLookAndFeelList(); final JList<String> list = lookAndFeel.getFirst(); final String currentKey = lookAndFeel.getThird(); final Map<String, String> lookAndFeels = lookAndFeel.getSecond(); if (JOptionPane.showConfirmDialog(m_parentFrame, list, lookAndFeelTitle, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.OK_OPTION) { final String selectedValue = list.getSelectedValue(); if (selectedValue == null) { return; } if (selectedValue.equals(currentKey)) { return; } LookAndFeel.setDefaultLookAndFeel(lookAndFeels.get(selectedValue)); EventThreadJOptionPane.showMessageDialog(m_parentFrame, "The look and feel has been applied. Please restart TripleA for it to take full effect", new CountDownLatchHandler(true)); } })); m_gameParser.addActionListener(SwingAction.of("Enable/Disable Delayed Parsing of Game XML's", e -> { // TODO: replace with 2 radio buttons final boolean current = GameRunner.getDelayedParsing(); final Object[] options = {"Parse Selected", "Parse All", "Cancel"}; final int answer = JOptionPane.showOptionDialog(m_parentFrame, new JLabel("<html>Delay Parsing of Game Data from XML until game is selected?" + "<br><br>'" + options[1] + "' means each map is fully parsed as TripleA starts (useful for testing to make sure all your maps are " + "valid, but can slow down the game significantly)." + "<br><br>Your current setting is: '" + (current ? options[0].toString() : options[1].toString()) + "'</html>"), "Select Parsing Method", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2]); if (answer == JOptionPane.CANCEL_OPTION) { return; } final boolean delay = (answer == JOptionPane.YES_OPTION); if (delay == current) { return; } GameRunner.setDelayedParsing(delay); EventThreadJOptionPane.showMessageDialog(m_parentFrame, "Please restart TripleA to avoid any potential errors", new CountDownLatchHandler(true)); })); m_setupProxies.addActionListener(SwingAction.of("Setup Network and Proxy Settings", e -> { // TODO: this action listener should probably come from the HttpProxy class final Preferences pref = Preferences.userNodeForPackage(GameRunner.class); final HttpProxy.ProxyChoice proxyChoice = HttpProxy.ProxyChoice.valueOf(pref.get(HttpProxy.PROXY_CHOICE, HttpProxy.ProxyChoice.NONE.toString())); final String proxyHost = pref.get(HttpProxy.PROXY_HOST, ""); final JTextField hostText = new JTextField(proxyHost); final String proxyPort = pref.get(HttpProxy.PROXY_PORT, ""); final JTextField portText = new JTextField(proxyPort); final JRadioButton noneButton = new JRadioButton("None", proxyChoice == HttpProxy.ProxyChoice.NONE); final JRadioButton systemButton = new JRadioButton("Use System Settings", proxyChoice == HttpProxy.ProxyChoice.USE_SYSTEM_SETTINGS); final JRadioButton userButton = new JRadioButton("Use These User Settings:", proxyChoice == HttpProxy.ProxyChoice.USE_USER_PREFERENCES); final ButtonGroup bgroup = new ButtonGroup(); bgroup.add(noneButton); bgroup.add(systemButton); bgroup.add(userButton); final JPanel radioPanel = new JPanel(); radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS)); radioPanel.add(new JLabel("Configure TripleA's Network and Proxy Settings: ")); radioPanel.add(new JLabel("(This only effects Play-By-Forum games, dice servers, and map downloads.)")); radioPanel.add(noneButton); radioPanel.add(systemButton); radioPanel.add(userButton); radioPanel.add(new JLabel("Proxy Host: ")); radioPanel.add(hostText); radioPanel.add(new JLabel("Proxy Port: ")); radioPanel.add(portText); final Object[] options = {"Accept", "Cancel"}; final int answer = JOptionPane.showOptionDialog(m_parentFrame, radioPanel, "Network Settings", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (answer != JOptionPane.YES_OPTION) { return; } final HttpProxy.ProxyChoice newChoice; if (systemButton.isSelected()) { newChoice = HttpProxy.ProxyChoice.USE_SYSTEM_SETTINGS; } else if (userButton.isSelected()) { newChoice = HttpProxy.ProxyChoice.USE_USER_PREFERENCES; } else { newChoice = HttpProxy.ProxyChoice.NONE; } HttpProxy.setProxy(hostText.getText(), portText.getText(), newChoice); })); m_hostWaitTime.addActionListener(SwingAction.of("Set Max Host Wait Time for Clients and Observers", e -> { final NumberProperty clientWait = new NumberProperty("Max seconds to wait for all clients to sync data on game start", "Max seconds to wait for all clients to sync data on game start", 9999, GameRunner.MINIMUM_SERVER_START_GAME_SYNC_WAIT_TIME, GameRunner.getServerStartGameSyncWaitTime()); final NumberProperty observerWait = new NumberProperty("Max seconds to wait for an observer joining a running game", "Max seconds to wait for an observer joining a running game", 9000, GameRunner.MINIMUM_SERVER_OBSERVER_JOIN_WAIT_TIME, GameRunner.getServerObserverJoinWaitTime()); final List<IEditableProperty> list = new ArrayList<>(); list.add(clientWait); list.add(observerWait); final PropertiesUI ui = new PropertiesUI(list, true); final Object[] options = {"Accept", "Reset to Defaults", "Cancel"}; final int answer = JOptionPane.showOptionDialog(m_parentFrame, ui, "Host Wait Settings", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2]); if (answer == JOptionPane.YES_OPTION) { GameRunner.setServerStartGameSyncWaitTime(clientWait.getValue()); GameRunner.setServerObserverJoinWaitTime(observerWait.getValue()); } else if (answer == JOptionPane.NO_OPTION) { // reset GameRunner.resetServerStartGameSyncWaitTime(); GameRunner.resetServerObserverJoinWaitTime(); } })); m_setMaxMemory.addActionListener(SwingAction.of("Set Max Memory Usage", e -> { // TODO: this action should all be coming from Memory.java final AtomicBoolean tested = new AtomicBoolean(); tested.set(false); final Properties systemIni = GameRunner.getSystemIni(); final int currentSetting = Memory.getMaxMemoryFromSystemIniFileInMB(systemIni); final boolean useDefault = Memory.useDefaultMaxMemory(systemIni) || currentSetting <= 0; final int currentMaxMemoryInMB = (int) (Memory.getMaxMemoryInBytes() / (1024 * 1024)); final IntTextField newMaxMemory = new IntTextField(0, (1024 * 3), currentMaxMemoryInMB, 5); final JRadioButton noneButton = new JRadioButton("Use Default", useDefault); final JRadioButton userButton = new JRadioButton("Use These User Settings:", !useDefault); final ButtonGroup bgroup = new ButtonGroup(); bgroup.add(noneButton); bgroup.add(userButton); final boolean onlineOnlyOriginalSetting = Memory.getUseMaxMemorySettingOnlyForOnlineJoinOrHost(systemIni); final JCheckBox onlyOnlineCheckBox = new JCheckBox("Only use these user memory settings for online games (join/host). [Default = On]"); onlyOnlineCheckBox.setSelected(onlineOnlyOriginalSetting); onlyOnlineCheckBox.setToolTipText( "<html>If checked, only joining and hosting from online lobby will be affected by these settings." + "<br />If unchecked, TripleA will automatically restart itself with the new memory setting every time " + "you start TripleA.</html>"); final JButton test = new JButton("Test User Settings"); test.addActionListener(SwingAction.of("Test User Settings", event -> { tested.set(true); System.out.println("Testing TripleA launch with max memory of: " + newMaxMemory.getValue() + "m"); // it is in MB GameRunner.startNewTripleA((((long) newMaxMemory.getValue()) * 1024 * 1024) + 67108864); })); final JPanel radioPanel = new JPanel(); radioPanel.setLayout(new BoxLayout(radioPanel, BoxLayout.Y_AXIS)); radioPanel.add(new JLabel("<html>Configure TripleA's Maxmimum Memory Usage Settings: " + "<br />(TripleA will only use 80-90% of this, the rest is used by Java VM)</html>")); radioPanel.add(new JLabel(" ")); radioPanel.add(new JLabel( "<html><b>WARNING: You could permanently stop TripleA from working if you mess with this! </b></html>")); radioPanel.add(new JLabel("<html><em><p>By default TripleA uses a bit less than 1gb of RAM memory, " + "<br />and this is because on some computers Java can fail when greater than 1gb (1024mb). " + "<br />The symptoms of this failing are: TripleA not starting, not being able to 'Join' or 'Host' " + "<br />in the online lobby, and not being able to start the map creator. " + "<br />For whatever max you set, Java requires you to have approximately double that much " + "<br />free memory available, not being used by your operating system or other programs you are running. " + "<br />Otherwise, TripleA will fail to start, and/or fail to join/host games online. " + "<br />If you do mess this up, you can always run TripleA by command line with a different setting: " + "<br />java -Xmx512m -classpath triplea.jar games.strategy.engine.framework.GameRunner " + "triplea.memory.set=true" + "<br />Or you can delete or change the 'system.ini' file located where TripleA was installed. </p>" + "<br /><p>In order to make sure you do not mess this up, click the 'Test' button and make sure that " + "<br />a new TripleA process is able to run with your new max memory setting. " + "<br />If one does not run, you had better lower the setting or just use the default. </p></em></html>")); radioPanel.add(new JLabel(" ")); radioPanel.add(onlyOnlineCheckBox); radioPanel.add(new JLabel(" ")); radioPanel.add(noneButton); radioPanel.add(userButton); radioPanel.add(new JLabel("Maximum Memory (in MB): ")); radioPanel.add(newMaxMemory); radioPanel.add(new JLabel(" ")); radioPanel.add(new JLabel("<html>After clicking the 'Test' button, a new TripleA should launch. " + "<br />If nothing launches, there is something wrong and you probably set the maximum too high. " + "<br />You MUST test user settings before you use them! Otherwise the engine will discard changes. " + "<br />TripleA has no way of knowing if this fails or succeeds, and there will not be an error message of " + "any kind. </html>")); radioPanel.add(test); radioPanel.add(new JLabel(" ")); final Object[] options = {"Accept", "Cancel"}; final int answer = JOptionPane.showOptionDialog(m_parentFrame, radioPanel, "Max Memory Settings", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]); if (answer != JOptionPane.YES_OPTION) { return; } if (noneButton.isSelected()) { Memory.clearMaxMemory(); } else if (userButton.isSelected()) { final boolean setOnlineOnly = onlineOnlyOriginalSetting != onlyOnlineCheckBox.isSelected(); final boolean setMaxMemory = newMaxMemory.getValue() > 64 && tested.get(); if (setOnlineOnly || setMaxMemory) { Properties prop; if (setMaxMemory) { prop = Memory.setMaxMemoryInMB(newMaxMemory.getValue()); } else { prop = new Properties(); } Memory.setUseMaxMemorySettingOnlyForOnlineJoinOrHost(onlyOnlineCheckBox.isSelected(), prop); GameRunner.writeSystemIni(prop); } } })); m_mapCreator .addActionListener(SwingAction.of("Run the Map Creator", e -> ProcessRunnerUtil.runClass(MapCreator.class))); m_mapXmlCreator.addActionListener( SwingAction.of("[Beta] Run the Map Creator", e -> ProcessRunnerUtil.runClass(MapXmlCreator.class))); m_console.addActionListener(SwingAction.of("Show Console", e -> { ErrorConsole.getConsole().setVisible(true); reportMemoryUsageToConsole(); })); } private static void reportMemoryUsageToConsole() { final int mb = 1024 * 1024; // Getting the runtime reference from system final Runtime runtime = Runtime.getRuntime(); System.out.println("Heap utilization statistics [MB]"); // Print used memory System.out.println("Used Memory: " + (runtime.totalMemory() - runtime.freeMemory()) / mb); // Print free memory System.out.println("Free Memory: " + runtime.freeMemory() / mb); // Print total available memory System.out.println("Total Memory: " + runtime.totalMemory() / mb); // Print Maximum available memory System.out.println("Max Memory: " + runtime.maxMemory() / mb); final int currentMaxSetting = Memory.getMaxMemoryFromSystemIniFileInMB(GameRunner.getSystemIni()); if (currentMaxSetting > 0) { System.out.println("Max Memory user setting within 20% of: " + currentMaxSetting); } } private void setWidgetActivation() {} public static void showEnginePreferences(final JComponent parent) { final Frame parentFrame = JOptionPane.getFrameForComponent(parent); final EnginePreferences enginePrefs = new EnginePreferences(parentFrame); enginePrefs.pack(); enginePrefs.setLocationRelativeTo(parentFrame); enginePrefs.setVisible(true); } }