diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 4c96ba383f0..c283ac15e35 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -647,6 +647,8 @@ ChatLounge.butGroundMap = Ground Map ChatLounge.butNames=Random Names... ChatLounge.butRemoveBot=Remove Bot ChatLounge.butSaveList=Save Unit List... +ChatLounge.butPrintList=Print Unit List... +ChatLounge.butPrintList.printing=Loading print dialog ChatLounge.butShrink=< ChatLounge.butSkills=Random Skills... ChatLounge.butShowUnitID=Show IDs @@ -1270,6 +1272,10 @@ CommonSettingsDialog.logFileName=Game log filename: CommonSettingsDialog.userDir=User Files Directory: CommonSettingsDialog.userDir.tooltip=Use this directory for resources you want to share between different installs or versions of MegaMek, MegaMekLab and MekHQ. Fonts, units, camos, portraits and fluff images will also be loaded from this directory.
Note: Inside the user directory, use the directory structure of MM/MML/MHQ for camos, portraits and fluff images, i.e. data/images/camo, data/images/portraits and data/images/fluff/.
Fonts and units can be placed anywhere in the user directory. CommonSettingsDialog.userDir.chooser.title=Choose User Data Folder +CommonSettingsDialog.mmlPath=Path to MegaMekLab Executable: +CommonSettingsDialog.mmlPath.tooltip=Used for printing unit lists.\ +
MegaMek will try to autodetect this when the option is blank if MM and MML are installed together. +CommonSettingsDialog.mmlPath.chooser.title=Select MegaMekLab Executable CommonSettingsDialog.main=Main CommonSettingsDialog.audio=Audio CommonSettingsDialog.miniMap=Mini Map diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index 4acc76028f5..3a6d459c4b3 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -2069,6 +2069,71 @@ public void saveListFile(ArrayList unitList, String filename) { } } + public void printList(ArrayList unitList, JButton button) { + if ((unitList == null) || unitList.isEmpty()) { + return; + } + + var mmlPath = CP.getMmlPath(); + var autodetect = false; + if (null == mmlPath || mmlPath.isBlank()) { + autodetect = true; + if (System.getProperty("os.name").toLowerCase().contains("win")) { + mmlPath = "MegaMekLab.exe"; + } else { + mmlPath = "MegaMekLab.sh"; + } + } + + var mml = new File(mmlPath); + + if (!mml.canExecute()) { + if (autodetect) { + logger.error("Could not auto-detect MegaMekLab! Please configure the path to the MegaMekLab executable in the settings.", "Error printing unit list"); + } else { + logger.error("%s does not appear to be an executable! Please configure the path to the MegaMekLab executable in the settings.".formatted(mml.getName()), "Error printing unit list"); + } + return; + } + + try { + var unitFile = File.createTempFile("MegaMekPrint", ".mul"); + EntityListFile.saveTo(unitFile, unitList); + String[] command; + if (mml.getName().toLowerCase().contains("gradle")) { + command = new String[] { + mml.getAbsolutePath(), + "run", + "--args=%s --no-startup".formatted(unitFile.getAbsolutePath()) + }; + } else { + command = new String[] { + mml.getAbsolutePath(), + unitFile.getAbsolutePath(), + "--no-startup" + }; + } + button.setText(Messages.getString("ChatLounge.butPrintList.printing")); + logger.info("Running command: {}", String.join(" ", command)); + var p = new ProcessBuilder(command) + .directory(mml.getAbsoluteFile().getParentFile()) + .inheritIO() + .start(); + new Thread(() -> { + try { + p.waitFor(); + } catch (InterruptedException e) { + logger.error(e); + } finally { + button.setText(Messages.getString("ChatLounge.butPrintList")); + } + }).start(); + + } catch (Exception e) { + logger.error(e, "Operation failed", "Error printing unit list"); + } + } + protected void saveVictoryList() { String filename = client.getLocalPlayer().getName(); diff --git a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java index 431ca7b313b..ae77658b6b6 100644 --- a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java +++ b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java @@ -212,6 +212,7 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg private JTextField tfSoundMuteOthersFileName; private JTextField userDir; + private JTextField mmlPath; private final JCheckBox keepGameLog = new JCheckBox(Messages.getString("CommonSettingsDialog.keepGameLog")); private JTextField gameLogFilename; private final JCheckBox stampFilenames = new JCheckBox(Messages.getString("CommonSettingsDialog.stampFilenames")); @@ -1724,6 +1725,23 @@ private JPanel getSettingsPanel() { addLineSpacer(comps); + JLabel mmlPathLabel = new JLabel(Messages.getString("CommonSettingsDialog.mmlPath")); + mmlPathLabel.setToolTipText(Messages.getString("CommonSettingsDialog.mmlPath.tooltip")); + mmlPath = new JTextField(20); + mmlPath.setMaximumSize(new Dimension(250, 40)); + mmlPath.setToolTipText(Messages.getString("CommonSettingsDialog.mmlPath.tooltip")); + JButton mmlPathChooser = new JButton("..."); + mmlPathChooser.addActionListener(e -> + fileChoose(mmlPath, getFrame(), Messages.getString("CommonSettingsDialog.mmlPath.chooser.title"), false)); + row = new ArrayList<>(); + row.add(mmlPathLabel); + row.add(mmlPath); + row.add(Box.createHorizontalStrut(10)); + row.add(mmlPathChooser); + comps.add(row); + + addLineSpacer(comps); + // UI Theme uiThemes = new JComboBox<>(); uiThemes.setMaximumSize(new Dimension(400, uiThemes.getMaximumSize().height)); @@ -1944,6 +1962,7 @@ public void setVisible(boolean visible) { gameLogFilename.setEnabled(keepGameLog.isSelected()); gameLogFilename.setText(CP.getGameLogFilename()); userDir.setText(CP.getUserDir()); + mmlPath.setText(CP.getMmlPath()); stampFilenames.setSelected(CP.stampFilenames()); stampFormat.setEnabled(stampFilenames.isSelected()); stampFormat.setText(CP.getStampFormat()); @@ -2421,6 +2440,7 @@ protected void okAction() { CP.setKeepGameLog(keepGameLog.isSelected()); CP.setGameLogFilename(gameLogFilename.getText()); CP.setUserDir(userDir.getText()); + CP.setMmlPath(mmlPath.getText()); CP.setStampFilenames(stampFilenames.isSelected()); CP.setStampFormat(stampFormat.getText()); CP.setReportKeywords(reportKeywordsTextPane.getText()); @@ -3452,13 +3472,19 @@ public static List filteredFilesWithSubDirs(File path, String fileEnding * @param parent The parent JFrame of the settings dialog */ public static void fileChooseUserDir(JTextField userDirTextField, JFrame parent) { - JFileChooser userDirChooser = new JFileChooser(userDirTextField.getText()); - userDirChooser.setDialogTitle(Messages.getString("CommonSettingsDialog.userDir.chooser.title")); - userDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fileChoose(userDirTextField, parent, Messages.getString("CommonSettingsDialog.userDir.chooser.title"),true); + } + + private static void fileChoose(JTextField textField, JFrame parent, String title, boolean directories) { + JFileChooser userDirChooser = new JFileChooser(textField.getText()); + userDirChooser.setDialogTitle(title); + if (directories) { + userDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + } int returnVal = userDirChooser.showOpenDialog(parent); if ((returnVal == JFileChooser.APPROVE_OPTION) && (userDirChooser.getSelectedFile() != null) - && userDirChooser.getSelectedFile().isDirectory()) { - userDirTextField.setText(userDirChooser.getSelectedFile().toString()); + && (directories ? userDirChooser.getSelectedFile().isDirectory() : userDirChooser.getSelectedFile().isFile())) { + textField.setText(userDirChooser.getSelectedFile().toString()); } } } diff --git a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java index 2066ee4cd5b..40285e77c44 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java +++ b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java @@ -160,6 +160,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements private JButton butNames = new JButton(Messages.getString("ChatLounge.butNames")); private JButton butLoadList = new JButton(Messages.getString("ChatLounge.butLoadList")); private JButton butSaveList = new JButton(Messages.getString("ChatLounge.butSaveList")); + private JButton butPrintList = new JButton(Messages.getString("ChatLounge.butPrintList")); /* Unit Table */ private MekTable mekTable; @@ -276,6 +277,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements private static final String CL_ACTIONCOMMAND_LOADLIST = "load_list"; private static final String CL_ACTIONCOMMAND_SAVELIST = "save_list"; + private static final String CL_ACTIONCOMMAND_PRINTLIST = "print_list"; private static final String CL_ACTIONCOMMAND_LOADMEK = "load_mek"; private static final String CL_ACTIONCOMMAND_ADDBOT = "add_bot"; private static final String CL_ACTIONCOMMAND_REMOVEBOT = "remove_bot"; @@ -365,6 +367,7 @@ private void setupListeners() { butRandomMap.addActionListener(lobbyListener); butRemoveBot.addActionListener(lobbyListener); butSaveList.addActionListener(lobbyListener); + butPrintList.addActionListener(lobbyListener); butShowUnitID.addActionListener(lobbyListener); butSkills.addActionListener(lobbyListener); butSpaceSize.addActionListener(lobbyListener); @@ -546,6 +549,8 @@ private void setupUnitConfig() { butLoadList.setEnabled(mscLoaded); butSaveList.setActionCommand(CL_ACTIONCOMMAND_SAVELIST); butSaveList.setEnabled(false); + butPrintList.setActionCommand(CL_ACTIONCOMMAND_PRINTLIST); + butPrintList.setEnabled(false); butAdd.setEnabled(mscLoaded); butAdd.setActionCommand(CL_ACTIONCOMMAND_LOADMEK); butArmy.setEnabled(mscLoaded); @@ -561,6 +566,7 @@ private void setupUnitConfig() { panUnitInfoGrid.add(butLoadList); panUnitInfoGrid.add(butSaveList); panUnitInfoGrid.add(butNames); + panUnitInfoGrid.add(butPrintList); panUnitInfo.add(panUnitInfoAdd); panUnitInfo.add(panUnitInfoGrid); @@ -1739,7 +1745,7 @@ public void actionPerformed(ActionEvent ev) { } clientgui.loadListFile(c.getLocalPlayer()); - } else if (ev.getSource().equals(butSaveList)) { + } else if (ev.getSource().equals(butSaveList) || ev.getSource().equals(butPrintList)) { // Allow the player to save their current // list of entities to a file. Client c = getSelectedClient(); @@ -1752,7 +1758,11 @@ public void actionPerformed(ActionEvent ev) { for (Entity entity : entities) { entity.setForceString(game().getForces().forceStringFor(entity)); } - clientgui.saveListFile(entities, c.getLocalPlayer().getName()); + if (ev.getSource().equals(butSaveList)) { + clientgui.saveListFile(entities, c.getLocalPlayer().getName()); + } else { + clientgui.printList(entities, (JButton) ev.getSource()); + } } else if (ev.getSource().equals(butAddBot)) { configAndCreateBot(null); @@ -2285,6 +2295,7 @@ public void removeAllListeners() { butRandomMap.removeActionListener(lobbyListener); butRemoveBot.removeActionListener(lobbyListener); butSaveList.removeActionListener(lobbyListener); + butPrintList.removeActionListener(lobbyListener); butShowUnitID.removeActionListener(lobbyListener); butSkills.removeActionListener(lobbyListener); butSpaceSize.removeActionListener(lobbyListener); @@ -2404,10 +2415,12 @@ private void refreshPlayerConfig() { // Disable the Remove Bot button for the "player" of a "Connect As Bot" client butRemoveBot.setEnabled(isSingleLocalBot); butSaveList.setEnabled(false); + butPrintList.setEnabled(false); if (isSinglePlayer) { var selPlayer = theElement(selPlayers); var hasUnits = !game().getPlayerEntities(selPlayer, false).isEmpty(); butSaveList.setEnabled(hasUnits && unitsVisible(selPlayer)); + butPrintList.setEnabled(hasUnits && unitsVisible(selPlayer)); setTeamSelectedItem(selPlayer.getTeam()); } } diff --git a/megamek/src/megamek/common/preference/ClientPreferences.java b/megamek/src/megamek/common/preference/ClientPreferences.java index c4629e65c8c..75d3b4b7943 100644 --- a/megamek/src/megamek/common/preference/ClientPreferences.java +++ b/megamek/src/megamek/common/preference/ClientPreferences.java @@ -70,6 +70,8 @@ public class ClientPreferences extends PreferenceStoreProxy { */ public static final String USER_DIR = "UserDir"; + public static final String MML_PATH = "MmlPath"; + // endregion Variable Declarations // region Constructors @@ -103,6 +105,7 @@ public ClientPreferences(IPreferenceStore store) { store.setDefault(IP_ADDRESSES_IN_CHAT, false); store.setDefault(START_SEARCHLIGHTS_ON, true); store.setDefault(USER_DIR, ""); + store.setDefault(MML_PATH, ""); setLocale(store.getString(LOCALE)); setMekHitLocLog(); } @@ -406,4 +409,12 @@ public void setUserDir(String userDir) { } store.setValue(USER_DIR, userDir); } + + public String getMmlPath() { + return store.getString(MML_PATH); + } + + public void setMmlPath(String mmlPath) { + store.setValue(MML_PATH, mmlPath.isBlank() ? "" : new File(mmlPath).getAbsolutePath()); + } }