diff --git a/megamek/data/forcegenerator/factions.xml b/megamek/data/forcegenerator/factions.xml index d41fb689209..c6543fc2b21 100644 --- a/megamek/data/forcegenerator/factions.xml +++ b/megamek/data/forcegenerator/factions.xml @@ -1,1460 +1,1460 @@ - + 3151- Provisional Garrison,Solahma,Front Line CJF - + 3058-3067 F,D,C,B,A MERC.KH - + 3139- ROS - - Azami Brotherhood + + Azami Brotherhood 2450-2516,3075-3081 D DC.AL - + 2830- Solahma CLAN - + 2779-3049 F,D,C,B,A Periphery.R,PIR - + 3128-3141 CS - + 3018-3028 F,D,C,B,A Periphery.R,PIR - + 3066- F,D,C,B,A Periphery.HR - + 2367- F,D,C,B,A IS - + 2392-3080 F,D,C,B,A Periphery.Deep - + - F,D,C,B,A Periphery.R - + 3057-3069 F,D,C,B,A IS - + 2785-3081 F,D,C,B,A Periphery.MW,WOB.pm - + 2807- Provisional Garrison,Solahma,Second Line,Front Line,Keshik - + 2807-3082 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-3059,3072-3074 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-3085 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-3085 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - - Ghost Bear Dominion + + Ghost Bear Dominion 2807-3103 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-3080 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-3074 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-2868 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN - + 2807-3143 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 3138- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - - Clan Diamond Shark - Clan Sea Fox + + Clan Diamond Shark + Clan Sea Fox 2807- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-3060 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-3082 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-3085 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-3075 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 3075-3085 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.HW - + 2807-2834 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN - + 2807-3142 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 3057-3151 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 2807-2823 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN - + 2788-3081 B,A IS - + 3137- FWL - + 2319- F,D,C,B,A IS - + 3030-3040,3079- F,D,C,B,A IS,FWL - + 3079-3082 F,D,C,B,A IS,FWL - + 3079-3086 F,D,C,B,A IS,FWL - + 3079-3086 F,D,C,B,A IS,FWL - + 3057-3066 CM - - Duchy of Tamarind-Abbey + + Duchy of Tamarind-Abbey 3071-3139 F,D,C,B,A IS,FWL - + -3049 F,D,C,B,A Periphery.DD - + 3080-3141 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CGS,Periphery.Deep - + 3028-3057 F,D,C,B,A FS,LA - - Federated Commonwealth (Davion) - Federated Suns + + Federated Commonwealth (Davion) + Federated Suns 2317- F,D,C,B,A IS - + 2988- F,D,C,B,A MERC,Periphery.OS - + 3072- F,D,C,B,A Periphery.HR - + 3034-3103 F,D,C,B,A IS - - Former Free Worlds Independent - Free Worlds League + + Former Free Worlds Independent + Free Worlds League 2271- F,D,C,B,A IS - + 3066- NCR - + 3144- F,D,C,B,A IS - + 3028-3049 F,D,C,B,A Periphery.R,PIR - + 2891-3141 F,D,C,B,A Periphery.Deep - + 2350-3063 F,D,C,B,A Periphery.MW - + - F,D,C,B,A - + 3072-3081 CM - + 2815-3055,3087- F,D,C,B,A Periphery.MW - - Federated Commonwealth (Steiner) - Lyran Alliance - Lyran Commonwealth + + Federated Commonwealth (Steiner) + Lyran Alliance + Lyran Commonwealth 2341- F,D,C,B,A IS - + 2530- F,D,C,B,A Periphery.CM - + 3073-3079 F,D,C,B,A FS.CrMM - + 3151- F,D,C MERC - + 2920- F,D,C,B,A Periphery.ME - + 3079-3082 F,D,C,B,A IS - + 3082-3138 F,D,C,B,A IS - + - F,D,C,B,A IS - + 2855- F,D,C,B,A Periphery.OS - + 3137-3139 FWL - + 3021-3028 F,D,C,B,A Periphery.R,PIR - + 3060-3066 F,D,C,B,A Periphery.CM - + 2760- F,D,C,B,A Periphery.CM - + 2775-2795,3012-3049 F,D,C,B,A Periphery.DD - + 3078-3081 FWL - + 3086-3139 F,D,C,B,A IS,FWL - + 2413-3082 F,D,C,B,A Periphery.OS - + 2801-2822 SL - + - F,D,C,B,A - + - F,D,C,B,A Periphery - + 3079-3086 F,D,C,B,A IS - + 2260-2510 F,D,C,B,A IS - + 3079-3086 F,D,C,B,A IS - + 3136-3144 FWL - + 3103- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 3083- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN.IS - + 3086- F,D,C,B,A IS,FWL - + 3079-3086 F,D,C,B,A IS,FWL - + 3135-3151 Triarii Protectors,Principes Guards,Hastati Sentinels,Stone's Brigade ROS - + 3081-3151 Triarii Protectors,Principes Guards,Hastati Sentinels,Stone's Brigade IS - + 3048- F,D,C,B,A Periphery.R - + 3075-3139 F,D,C,B,A IS,FWL - + 3087- F,D,C,B,A Periphery.R - + 2250-2769 F,D,C,B,A Periphery.R - + 3057-3068 CM - + 3141- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CEI,Periphery - + 3135-3138 ROS - + 3135-3144 ROS - + 3079-3084 FWL - + 3029-3063 F,D,C,B,A IS - + 2571-2780 C,B,A - + 3151- C,B,A CWE,ROS - + 2786-2801 SL - + 3079-3082 F,D,C,B,A IS - + 3073-3081 F,D,C,B,A ROS - + 3057-3063 CM - + 3136-3144 ROS - + 3151- F,D,A LA - + 2335- F,D,C,B,A Periphery.HR - + 3057-3068 CM - + 2086-2315 F,D,C,B,A IS - - Amaris Empire - Terran Hegemony + + Amaris Empire + Terran Hegemony 2315-2788 F,D,C,B,A IS,SL,SL.R - + 3100- F,D,C,B,A Periphery.DD - + 3078-3086 F,D,C,B,A IS - + 3072-3075 PG CLAN - + 3029-3031 CC - + 2785- F,D,C,B,A Periphery.HR,PIR - + 2830-3080 F,D,C,B,A NC,Periphery.Deep - + 2240-2540 F,D,C,B,A FS - + 3151- F,D,A LA - + 3143- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CW - + 3052-3081 B,A IS - + 2317- D FS - + 3058- C LA - - Lyran Reserves + + Lyran Reserves 3057- B LA - + 3083- F,D,C,B,A OA - - Alshain Avengers + + Alshain Avengers 3034-3064 D DC - + 2319- B DC - + 2823- C DC - + 2556-3000 C CC - + 2317-2830 C FS - + 2341- C LA - + 2479- D DC - + 3082-3138 B MSC - + 2271-3079,3138- B FWL - + 3082-3138 C MSC - + 2923-3079 C FWL - + 2317- B FS - + 2319- C DC - + 2558-2950 D FWL - + 2530- B MOC - + 3061- D CC - + 2367-2835,3135- B CC - + 3057- D CC - + 2367- F CC - + 2367- A CC - + 3079- B FS - + 2754- D FS - + - F,D,C,B,A Periphery - + 2730- D FS - + 2530- B MOC - - Chesteron Reserves + + Chesteron Reserves 2832-3060 C CC - + 2897-3067 D FS - + 3060- B CC - + 3050-3085 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN - + 3050- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CLAN - + 3139- Provisional Garrison,Solahma,Second Line,Front Line,Keshik CP - + 2650- F CC - + 3139- C FWL - + 2780- B FS - + 2754- D FS - + 2418- A FS - + 2988- A CC - + - F,D,C,B,A Periphery - + 2514-3039 F FWL - + 2780-3081 C FS - + 2319-3081,3135- D DC - + 2341- B LA - + - F,D,C,B,A Periphery - + 2754- F FS - + 2572-2870 B FS - + 2780-3081 MERC - + 2959- B,A CS - + 3028-3057 A FC - + 3082-3138 A MSC - + 2426-3079,3138- A FWL - + 3082-3138 B MSC - + 3042-3079 B FWL - + 3086-3139 A OP - + 2485-3079,3139- A FWL - + 2319- B DC - + 3027- A DC - + 3033- C DC - + 2820- Keshik CLAN.HW - + 3024-3065 MERC - + 2755-2950 F LA - + - F,D,C,B,A Periphery - + 3030- A DC - + 3010- MERC,LA - + 2823-3066 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3067 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3067 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 3067-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3066 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 3066-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-3073 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 2823-2872 Provisional Garrison,Solahma,Second Line,Front Line,Keshik CFM - + 3055-3079 A FWL - + 3079-3103 A FRR - + 3011- D DC - + 3061- D CC - + 2367-2823,3113- D CC - + 2529-3000 D CC - + 2341- D LA - + 2341- F LA - + 2755- B MOC - + 2530- A MOC - + - F,D,C,B,A Periphery - + - F,D,C,B,A Periphery - + 2246-3079 C FWL - + 2238-3079,3139- F FWL - + 3082-3138 F MSC - + 3060- A CC - + 3020- B FS - + 3082- A DC - + 3020-3076 D DC - + -3081 MERC - + 2755-2950 B LA - + 2245-3079,3139- F FWL - + 3086-3139 F OP - + 2691-3079,3139- A FWL - + 3086-3139 A OP - + 2571-2900,3050- A DC - + - F,D,C,B,A Periphery - + 3080- A FS - + 3080- C FS - + 2319- F DC - + 2319- F DC,IS.pm - + 3082-3138 F MSC,IS.pm - + 2317- F FS,IS.pm - + 3079- F DA,IS.pm - + 3075-3139 F RCM,IS.pm - + 3028-3057 F FS.pm,LA.pm - + 2271- F FWL,IS.pm - + 3086- F RF,IS.pm - + 3081- PG ROS,IS.pm - + 2341- F LA,IS.pm - + 2335- F TC - + - F IS - + 2315-2767 F TH,IS.pm - + 2367- F CC,IS.pm - + 3071-3139 F DTA,IS.pm - + 2530- F MOC - + 2335- TC - + 2319-3082 C DC - + 3000-3079,3138- C FWL - + 3066-3078 B WOB - + 2730-3034 D DC - - Raventhir Cuirassiers + + Raventhir Cuirassiers 3055- C MOC - + 2341- F LA - + 2478-3079 D FWL - + 3029-3067 C CC - + 3139- C FWL - + 2250-2769 F,D,C,B,A Periphery.R - + 2765-2779 B,A SL.R,Periphery.R - + 2540-2950 C FS - + 3080- A FS - + 2780- C FS - + 2341- A LA - + 3026- B DC - + 2367-2529 D CC - + 3058-3067 C,B,A IS - + - F MERC,IS.pm - + 3067-3081 A WOB - + 3036-3061 C DC - - Sian Dragoons + + Sian Dragoons 2367-3000 D CC - + 3000-3081,3138- C FWL - + 2950-3079 D FWL - + 2341-3081 C LA - + 2367- B CC - + 3055-3079 B CC - + 3062- B CC - + 3151- Front Line,Keshik CJF,CLAN.IS - + 2571-2780 SL - + 2293-3079 D FWL - + 2319- D DC - + 2319- A DC - + 2418- C FS - + 3139- C FWL - + 2540-3000 C FS - + - F,D,C,B,A Periphery - + 3134- C CC - + 2367-3000 D CC - + 2341- C LA - + 3150-3152 Provisional Garrison,Solahma CJF - + 3060- C CC - + 2864- B CC - + 3005- MERC - + 2700-2950 F LA diff --git a/megamek/src/megamek/MMConstants.java b/megamek/src/megamek/MMConstants.java index 1f1e8e054f6..4f6d0e11948 100644 --- a/megamek/src/megamek/MMConstants.java +++ b/megamek/src/megamek/MMConstants.java @@ -24,7 +24,6 @@ public final class MMConstants extends SuiteConstants { //region General Constants public static final String PROJECT_NAME = "MegaMek"; - public static final String MUL_URL_PREFIX = "http://www.masterunitlist.info/Unit/Details/"; //endregion General Constants //region GUI Constants diff --git a/megamek/src/megamek/SuiteConstants.java b/megamek/src/megamek/SuiteConstants.java index 436834e7481..3ecef0025b2 100644 --- a/megamek/src/megamek/SuiteConstants.java +++ b/megamek/src/megamek/SuiteConstants.java @@ -59,4 +59,7 @@ public abstract class SuiteConstants { public static final String MM_PREFERENCES_FILE = "mmconf/mm.preferences"; public static final String MML_PREFERENCES_FILE = "mmconf/mml.preferences"; //endregion File Paths + + public static final String MUL_URL_UNIT_PREFIX = "http://www.masterunitlist.info/Unit/Details/"; + public static final String MUL_URL_FACTION_PREFIX = "http://www.masterunitlist.info/Faction/Details/"; } diff --git a/megamek/src/megamek/client/ratgenerator/FactionRecord.java b/megamek/src/megamek/client/ratgenerator/FactionRecord.java index 2da25cc9c1a..d5981540545 100644 --- a/megamek/src/megamek/client/ratgenerator/FactionRecord.java +++ b/megamek/src/megamek/client/ratgenerator/FactionRecord.java @@ -67,16 +67,18 @@ TechCategory fallthrough() { } private String key; - private boolean minor; - private boolean clan; - private boolean periphery; + private int mulId = -1; + private boolean minor = false; + private boolean clan = false; + private boolean periphery = false; private String name; - private TreeMap altNames; - private ArrayList yearsActive; - private ArrayList ratingLevels; - private HashMap pctSalvage; - private HashMap>> pctTech; - private HashMap> salvage; + private final TreeMap altNames = new TreeMap<>(); + private final TreeMap altMulIds = new TreeMap<>(); + private final ArrayList yearsActive = new ArrayList<>(); + private final ArrayList ratingLevels = new ArrayList<>(); + private final HashMap pctSalvage = new HashMap<>(); + private final HashMap>> pctTech = new HashMap<>(); + private final HashMap> salvage = new HashMap<>(); /* * FM:Updates gives percentage values for omni, Clan, and SL tech. Later manuals are * less precise, giving omni percentages for Clans and (in FM:3085) upgrade percentage @@ -86,12 +88,12 @@ TechCategory fallthrough() { * get farther from known values. upgradeMargin applies the percentage of units that * are late-SW IS tech. techMargin applies to both Clan and advanced (SL and post-Clan) tech. */ - private HashMap omniMargin; - private HashMap techMargin; - private HashMap upgradeMargin; + private final HashMap omniMargin = new HashMap<>(); + private final HashMap techMargin = new HashMap<>(); + private final HashMap upgradeMargin = new HashMap<>(); - private HashMap>> weightDistribution; - private ArrayList parentFactions; + private final HashMap>> weightDistribution = new HashMap<>(); + private final ArrayList parentFactions = new ArrayList<>(); public FactionRecord() { this("Periphery", "Periphery"); @@ -104,18 +106,6 @@ public FactionRecord(String key) { public FactionRecord(String key, String name) { this.key = key; this.name = name; - minor = clan = periphery = false; - ratingLevels = new ArrayList<>(); - altNames = new TreeMap<>(); - yearsActive = new ArrayList<>(); - pctSalvage = new HashMap<>(); - pctTech = new HashMap<>(); - omniMargin = new HashMap<>(); - upgradeMargin = new HashMap<>(); - techMargin = new HashMap<>(); - salvage = new HashMap<>(); - weightDistribution = new HashMap<>(); - parentFactions = new ArrayList<>(); } @Override @@ -133,6 +123,35 @@ public String getKey() { return key; } + /** @return The MUL ID of this faction. May vary with the given year when this faction has alternate names and MUL IDs. */ + public int getMulId(int year) { + Map.Entry possibleAltMulId = altMulIds.floorEntry(year); + return possibleAltMulId != null ? possibleAltMulId.getValue() : mulId; + } + + /** @return The MUL ID of this faction. Always returns the base MUL ID, never any year-dependent alternates. */ + public int getMulId() { + return mulId; + } + + public void setMulId(int newId) { + mulId = newId; + } + + public void setMulId(int year, int mulId) { + altMulIds.put(year, mulId); + } + + public void setMulIds(String mulIds) { + String[] fields = mulIds.split(","); + mulId = Integer.parseInt(fields[0]); + altMulIds.clear(); + for (int i = 1; i < fields.length; i++) { + String[] entry = fields[i].split(":"); + altMulIds.put(Integer.parseInt(entry[0]), Integer.parseInt(entry[1])); + } + } + public boolean isMinor() { return minor; } @@ -479,6 +498,7 @@ public static FactionRecord createFromXml(Node node) { FactionRecord retVal = new FactionRecord(); retVal.key = node.getAttributes().getNamedItem("key").getTextContent(); retVal.name = node.getAttributes().getNamedItem("name").getTextContent(); + retVal.mulId = Integer.parseInt(node.getAttributes().getNamedItem("mulid").getTextContent()); if (node.getAttributes().getNamedItem("minor") != null) { retVal.minor = Boolean.parseBoolean(node.getAttributes().getNamedItem("minor").getTextContent()); } else { @@ -502,6 +522,8 @@ public static FactionRecord createFromXml(Node node) { if (wn.getNodeName().equalsIgnoreCase("nameChange")) { retVal.altNames.put(Integer.parseInt(wn.getAttributes().getNamedItem("year").getTextContent()), wn.getTextContent()); + retVal.altMulIds.put(Integer.parseInt(wn.getAttributes().getNamedItem("year").getTextContent()), + Integer.parseInt(wn.getAttributes().getNamedItem("mulid").getTextContent())); } else if (wn.getNodeName().equalsIgnoreCase("years")) { try { retVal.setYears(wn.getTextContent()); @@ -586,13 +608,10 @@ public void loadEra(Node node, int era) { } public void writeToXml(PrintWriter pw) { - pw.println("\t"); + pw.println("\t"); for (Integer year : altNames.keySet()) { - pw.println("\t\t" - + altNames.get(year) + ""); + pw.println("\t\t" + altNames.get(year) + ""); } pw.print("\t\t"); pw.print(getYearsAsString()); @@ -601,7 +620,7 @@ public void writeToXml(PrintWriter pw) { pw.println("\t\t" + StringEscapeUtils.escapeXml10(String.join(",", ratingLevels)) + ""); } - if ((parentFactions != null) && !parentFactions.isEmpty()) { + if (!parentFactions.isEmpty()) { pw.println("\t\t" + StringEscapeUtils.escapeXml10(String.join(",", parentFactions)) + ""); } pw.println("\t"); @@ -775,6 +794,18 @@ public Object getNamesAsString() { return retVal.toString(); } + /** + * @return CSV of all MUL IDs of the faction, with the original MUL ID given first followed by + * changes in the format year:MUL ID + */ + public Object getMulIdsAsString() { + StringBuilder retVal = new StringBuilder(mulId+""); + for (Integer y : altMulIds.keySet()) { + retVal.append(",").append(y).append(":").append(altMulIds.get(y)); + } + return retVal.toString(); + } + /** * @return CSV String of all date ranges in which the faction is active. */ diff --git a/megamek/src/megamek/client/ratgenerator/RATDataCSVExporter.java b/megamek/src/megamek/client/ratgenerator/RATDataCSVExporter.java index 9c1ab681e85..23b88f20ff6 100644 --- a/megamek/src/megamek/client/ratgenerator/RATDataCSVExporter.java +++ b/megamek/src/megamek/client/ratgenerator/RATDataCSVExporter.java @@ -95,6 +95,7 @@ public static void exportToCSV(RATGenerator ratGenerator) { } } catch (Exception ex) { LogManager.getLogger().error("", ex); + JOptionPane.showMessageDialog(null, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); } } @@ -143,7 +144,7 @@ private static void writeModelBaseData(ModelRecord record, StringBuilder csvLine csvLine.append(record.getMechSummary().getMulId()).append(DELIMITER); csvLine.append(UnitType.getTypeName(record.getUnitType())).append(DELIMITER); csvLine.append(record.getMechSummary().getYear()).append(DELIMITER); - csvLine.append("TBD").append(DELIMITER); + csvLine.append(mulId(faction)).append(DELIMITER); csvLine.append(faction).append(DELIMITER); } @@ -154,10 +155,15 @@ private static void writeChassisBaseData(ChassisRecord record, StringBuilder csv csvLine.append(DELIMITER); csvLine.append(UnitType.getTypeName(record.getUnitType())).append(DELIMITER); csvLine.append(DELIMITER); - csvLine.append("TBD").append(DELIMITER); + csvLine.append(mulId(faction)).append(DELIMITER); csvLine.append(faction).append(DELIMITER); } + private static int mulId(String faction) { + FactionRecord factionRecord = RATGenerator.getInstance().getFaction(faction); + return factionRecord != null ? factionRecord.getMulId() : -2; + } + private static RATGenerator initializeRatGenerator() { RATGenerator ratGenerator = RATGenerator.getInstance(); while (!ratGenerator.isInitialized()) { diff --git a/megamek/src/megamek/client/ui/swing/MechViewPanel.java b/megamek/src/megamek/client/ui/swing/MechViewPanel.java index 5d1918d8943..1c86ce045c4 100644 --- a/megamek/src/megamek/client/ui/swing/MechViewPanel.java +++ b/megamek/src/megamek/client/ui/swing/MechViewPanel.java @@ -20,12 +20,12 @@ package megamek.client.ui.swing; import megamek.client.ui.swing.util.FluffImageHelper; +import megamek.client.ui.swing.util.UIUtil; import megamek.client.ui.swing.util.UIUtil.FixedXPanel; import megamek.common.Entity; import megamek.common.MechView; import megamek.common.Report; import megamek.common.templates.TROView; -import org.apache.logging.log4j.LogManager; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -60,15 +60,8 @@ public MechViewPanel(int width, int height, boolean noBorder) { txtMek.setMinimumSize(new Dimension(width, height)); txtMek.setPreferredSize(new Dimension(width, height)); txtMek.addHyperlinkListener(e -> { - try { - if (HyperlinkEvent.EventType.ACTIVATED == e.getEventType()) { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(e.getURL().toURI()); - } - } - } catch (Exception ex) { - LogManager.getLogger().error("", ex); - JOptionPane.showMessageDialog(this, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); + if (HyperlinkEvent.EventType.ACTIVATED == e.getEventType()) { + UIUtil.openInBrowser(e.getURL().toString(), null); } }); scrMek = new JScrollPane(txtMek); diff --git a/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java b/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java index a1095c44068..1f491ac6d8b 100644 --- a/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java +++ b/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java @@ -19,22 +19,20 @@ package megamek.client.ui.swing.alphaStrike; import megamek.MMConstants; +import megamek.client.ui.Messages; import megamek.client.ui.dialogs.ASConversionInfoDialog; import megamek.client.ui.swing.GUIPreferences; import megamek.client.ui.swing.util.UIUtil; -import megamek.client.ui.Messages; import megamek.common.alphaStrike.ASCardDisplayable; import megamek.common.alphaStrike.ASStatsExporter; import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.alphaStrike.cardDrawer.ASCardPrinter; import megamek.common.annotations.Nullable; -import org.apache.logging.log4j.LogManager; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.datatransfer.*; -import java.net.URL; import java.util.List; /** @@ -86,7 +84,7 @@ public ConfigurableASCardPanel(@Nullable ASCardDisplayable element, JFrame paren copyStatsButton.addActionListener(ev -> copyStats()); printButton.addActionListener(ev -> printCard()); - mulButton.addActionListener(ev -> showMUL()); + mulButton.addActionListener(ev -> UIUtil.showUnitInMul(mulId, parent)); mulButton.setToolTipText("Show the Master Unit List entry for this unit. Opens a browser window."); conversionButton.addActionListener(e -> showConversionReport()); @@ -160,17 +158,6 @@ private void updateSize() { } } - private void showMUL() { - try { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { - Desktop.getDesktop().browse(new URL(MMConstants.MUL_URL_PREFIX + mulId).toURI()); - } - } catch (Exception ex) { - LogManager.getLogger().error("", ex); - JOptionPane.showMessageDialog(this, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); - } - } - private void showConversionReport() { if (element instanceof AlphaStrikeElement) { var dialog = new ASConversionInfoDialog(parent, ((AlphaStrikeElement) element).getConversionReport(), element); diff --git a/megamek/src/megamek/client/ui/swing/util/UIUtil.java b/megamek/src/megamek/client/ui/swing/util/UIUtil.java index e0cd041841d..69c02e26f4b 100644 --- a/megamek/src/megamek/client/ui/swing/util/UIUtil.java +++ b/megamek/src/megamek/client/ui/swing/util/UIUtil.java @@ -22,6 +22,7 @@ import megamek.common.Player; import megamek.common.annotations.Nullable; import megamek.common.util.ImageUtil; +import org.apache.logging.log4j.LogManager; import javax.swing.*; import javax.swing.border.Border; @@ -35,6 +36,7 @@ import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.image.ImageObserver; +import java.net.URL; import java.util.List; import java.util.*; import java.util.stream.Stream; @@ -44,10 +46,14 @@ public final class UIUtil { // The standard pixels-per-inch to compare against for display scaling private static final int DEFAULT_DISPLAY_PPI = 96; - /** The width for a tooltip displayed to the side of a dialog using one of TipXX classes. */ + /** + * The width for a tooltip displayed to the side of a dialog using one of TipXX classes. + */ private static final int TOOLTIP_WIDTH = 300; - - /** The style = font-size: xx value corresponding to a GUI scale of 1 */ + + /** + * The style = font-size: xx value corresponding to a GUI scale of 1 + */ public final static int FONT_SCALE1 = 14; public final static int FONT_SCALE2 = 17; public final static String ECM_SIGN = " \u24BA "; @@ -64,68 +70,72 @@ public static String repeat(String str, int count) { return String.valueOf(str).repeat(Math.max(0, count)); } - /** - * Returns an HTML FONT tag setting the font face to Dialog - * and the font size according to GUIScale. + /** + * Returns an HTML FONT tag setting the font face to Dialog + * and the font size according to GUIScale. */ public static String guiScaledFontHTML() { return ""; } - - /** + + /** * Returns an HTML FONT tag setting the color to the given col, - * the font face to Dialog and the font size according to GUIScale. + * the font face to Dialog and the font size according to GUIScale. */ public static String guiScaledFontHTML(Color col) { return ""; } - - /** - * Returns an HTML FONT tag setting the font face to Dialog - * and the font size according to GUIScale. + + /** + * Returns an HTML FONT tag setting the font face to Dialog + * and the font size according to GUIScale. */ public static String guiScaledFontHTML(float deltaScale) { return ""; } - - /** + + /** * Returns an HTML FONT tag setting the color to the given col, - * the font face to Dialog and the font size according to GUIScale. + * the font face to Dialog and the font size according to GUIScale. */ public static String guiScaledFontHTML(Color col, float deltaScale) { return ""; } - - /** Returns the yellow and gui-scaled warning sign. */ + + /** + * Returns the yellow and gui-scaled warning sign. + */ public static String warningSign() { return guiScaledFontHTML(uiYellow()) + WARNING_SIGN + ""; } - - /** Returns the (usually) red and gui-scaled warning sign. */ + + /** + * Returns the (usually) red and gui-scaled warning sign. + */ public static String criticalSign() { return guiScaledFontHTML(GUIPreferences.getInstance().getWarningColor()) + WARNING_SIGN + ""; } - - /** + + /** * Helper method to place Strings in lines according to length. The Strings - * in origList will be added to one line with separator sep between them as - * long as the total length does not exceed maxLength. If it exceeds maxLength, - * a new line is begun. All lines but the last will end with sep if sepAtEnd is true. + * in origList will be added to one line with separator sep between them as + * long as the total length does not exceed maxLength. If it exceeds maxLength, + * a new line is begun. All lines but the last will end with sep if sepAtEnd is true. */ - public static ArrayList arrangeInLines(List origList, int maxLength, - String sep, boolean sepAtEnd) { - + public static ArrayList arrangeInLines(List origList, int maxLength, + String sep, boolean sepAtEnd) { + ArrayList result = new ArrayList<>(); if (origList == null || origList.isEmpty()) { return result; } String currLine = ""; - for (String curr: origList) { + for (String curr : origList) { // Skip empty strings to avoid double separators if (curr.isEmpty()) { continue; } - + if (currLine.isEmpty()) { // No entry in this line yet currLine = curr; @@ -151,14 +161,14 @@ public static ArrayList arrangeInLines(List origList, int maxLen } return result; } - - public static ArrayList arrangeInLines(int maxLength, - String sep, boolean sepAtEnd, String... origList) { - + + public static ArrayList arrangeInLines(int maxLength, + String sep, boolean sepAtEnd, String... origList) { + return arrangeInLines(Arrays.asList(origList), maxLength, sep, sepAtEnd); } - - /** + + /** * Returns a UIManager Color that can be used as an alternate row color in a table * to offset each other row. */ @@ -178,11 +188,11 @@ public static Color alternateTableBGColor() { // The really last fallback position return uiGray(); } - - /** - * Returns the Color associated with either enemies, allies or + + /** + * Returns the Color associated with either enemies, allies or * oneself from the GUIPreferences depending on the relation - * of the given player1 and player2. + * of the given player1 and player2. */ public static Color teamColor(Player player1, Player player2) { if (player1.getId() == player2.getId()) { @@ -193,64 +203,64 @@ public static Color teamColor(Player player1, Player player2) { return GUIPreferences.getInstance().getAllyUnitColor(); } } - - /** + + /** * Returns a green color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiGreen() { return uiBgBrightness() > 130 ? LIGHTUI_GREEN : DARKUI_GREEN; } - - /** + + /** * Returns a gray color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiGray() { return uiBgBrightness() > 130 ? LIGHTUI_GRAY : DARKUI_GRAY; } - - /** + + /** * Returns a light blue color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiLightBlue() { return uiBgBrightness() > 130 ? LIGHTUI_LIGHTBLUE : DARKUI_LIGHTBLUE; } - - /** + + /** * Returns a light red color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiLightRed() { return uiBgBrightness() > 130 ? LIGHTUI_LIGHTRED : DARKUI_LIGHTRED; } - /** + /** * Returns a light violet color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiLightViolet() { return uiBgBrightness() > 130 ? LIGHTUI_LIGHTVIOLET : DARKUI_LIGHTVIOLET; } - - /** + + /** * Returns a light green color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiLightGreen() { return uiBgBrightness() > 130 ? LIGHTUI_LIGHTGREEN : DARKUI_LIGHTGREEN; } - - /** + + /** * Returns a yellow color suitable as a text color. The supplied - * color depends on the UI look and feel and will be lighter for a + * color depends on the UI look and feel and will be lighter for a * dark UI LAF than for a light UI LAF. */ public static Color uiYellow() { @@ -274,41 +284,41 @@ public static Color uiBlack() { public static Color uiWhite() { return uiBgBrightness() > 130 ? LIGHTUI_WHITE : DARKUI_WHITE; } - - /** - * Returns a color for the UI display of Quirks/Advantages. Different + + /** + * Returns a color for the UI display of Quirks/Advantages. Different * colors will be supplied for a dark and for a light UI look-and-feel. */ public static Color uiQuirksColor() { return uiBgBrightness() > 130 ? LIGHTUI_LIGHTCYAN : DARKUI_LIGHTCYAN; } - - /** - * Returns a color for the UI display of Partial Repairs. Different + + /** + * Returns a color for the UI display of Partial Repairs. Different * colors will be supplied for a dark and for a light UI look-and-feel. */ public static Color uiPartialRepairColor() { return uiLightRed(); } - - /** - * Returns a color for the UI display of C3 Info. Different + + /** + * Returns a color for the UI display of C3 Info. Different * colors will be supplied for a dark and for a light UI look-and-feel. */ public static Color uiC3Color() { return uiLightViolet(); } - - /** - * Returns a color for the UI display of C3 Info. Different + + /** + * Returns a color for the UI display of C3 Info. Different * colors will be supplied for a dark and for a light UI look-and-feel. */ public static Color uiNickColor() { return uiLightGreen(); } - - /** - * Returns a color for the UI display of C3 Info. Different + + /** + * Returns a color for the UI display of C3 Info. Different * colors will be supplied for a dark and for a light UI look-and-feel. */ public static Color uiTTWeaponColor() { @@ -327,19 +337,19 @@ public static Color uiDarkBlue() { public static int scaleForGUI(int value) { return Math.round(scaleForGUI((float) value)); } - + public static float scaleForGUI(float value) { return GUIPreferences.getInstance().getGUIScale() * value; } - + public static Dimension scaleForGUI(Dimension dim) { float scale = GUIPreferences.getInstance().getGUIScale(); return new Dimension((int) (scale * dim.width), (int) (scale * dim.height)); } - - /** + + /** * Returns the provided color with its alpha value set to the provided alpha. - * alpha should be from 0 to 255 with 0 meaning transparent. + * alpha should be from 0 to 255 with 0 meaning transparent. */ public static Color addAlpha(Color color, int alpha) { Objects.requireNonNull(color); @@ -348,10 +358,10 @@ public static Color addAlpha(Color color, int alpha) { } return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha); } - - /** + + /** * Returns a grayed-out version of the given color. gray should be from 0 to 255 - * with 255 meaning completely gray. Does not change the brightness, nor alpha. + * with 255 meaning completely gray. Does not change the brightness, nor alpha. */ public static Color addGray(Color color, int gray) { Objects.requireNonNull(color); @@ -364,27 +374,34 @@ public static Color addGray(Color color, int gray) { int blue = (color.getBlue() * (255 - gray) + mid) / 255; return new Color(red, green, blue, color.getAlpha()); } - - /** Returns the given String str enclosed in HTML tags and with a font tag according to the guiScale. */ + + /** + * Returns the given String str enclosed in HTML tags and with a font tag according to the guiScale. + */ public static String scaleStringForGUI(String str) { return "" + UIUtil.guiScaledFontHTML() + str + ""; } - - /** Returns the given String str enclosed in HTML tags and with a font tag according to the guiScale. */ + + /** + * Returns the given String str enclosed in HTML tags and with a font tag according to the guiScale. + */ public static String scaleMessageForGUI(String str) { return "" + UIUtil.guiScaledFontHTML() + Messages.getString(str) + ""; } - - /** Call this for {@link #adjustContainer(Container, int)} with a dialog as parameter. */ + + /** + * Call this for {@link #adjustContainer(Container, int)} with a dialog as parameter. + */ public static void adjustDialog(JDialog dialog, int fontSize) { adjustContainer(dialog.getContentPane(), fontSize); } - /** calculate the max row height in a table + pad */ - public static int calRowHeights(JTable table, int sf, int pad) - { + /** + * calculate the max row height in a table + pad + */ + public static int calRowHeights(JTable table, int sf, int pad) { int rowHeight = sf; - for (int row = 0; row < table.getRowCount(); row++) { + for (int row = 0; row < table.getRowCount(); row++) { for (int col = 0; col < table.getColumnCount(); col++) { // Consider the preferred height of the column TableCellRenderer renderer = table.getCellRenderer(row, col); @@ -396,24 +413,26 @@ public static int calRowHeights(JTable table, int sf, int pad) return rowHeight + pad; } - /** set font size for the TitledBorder */ - public static void setTitledBorder(Border border, int sf) { + /** + * set font size for the TitledBorder + */ + public static void setTitledBorder(Border border, int sf) { if ((border instanceof TitledBorder)) { ((TitledBorder) border).setTitleFont(((TitledBorder) border).getTitleFont().deriveFont((float) sf)); } } - /** + /** * Applies the current gui scale to a given Container. * For a dialog, pass getContentPane(). This can work well for simple dialogs, - * but it is of course "experimental". Complex dialogs must be hand-adapted to the + * but it is of course "experimental". Complex dialogs must be hand-adapted to the * gui scale. */ public static void adjustContainer(Container parentCon, int fontSize) { int sf = scaleForGUI(fontSize); int pad = 3; - for (Component comp: parentCon.getComponents()) { + for (Component comp : parentCon.getComponents()) { if ((comp instanceof JButton) || (comp instanceof JLabel) || (comp instanceof JComboBox) || (comp instanceof JTextField) || (comp instanceof JSlider) || (comp instanceof JSpinner) || (comp instanceof JTextArea) || (comp instanceof JToggleButton) @@ -423,7 +442,7 @@ public static void adjustContainer(Container parentCon, int fontSize) { comp.setFont(comp.getFont().deriveFont((float) sf)); } } - if (comp instanceof JScrollPane + if (comp instanceof JScrollPane && ((JScrollPane) comp).getViewport().getView() instanceof JComponent) { JScrollPane scrollPane = (JScrollPane) comp; Border border = scrollPane.getBorder(); @@ -439,7 +458,7 @@ public static void adjustContainer(Container parentCon, int fontSize) { comp.setFont(comp.getFont().deriveFont((float) sf)); } JTabbedPane tabbedPane = (JTabbedPane) comp; - for (int i=0; i < tabbedPane.getTabCount();i++) { + for (int i = 0; i < tabbedPane.getTabCount(); i++) { Component subComp = tabbedPane.getTabComponentAt(i); if (subComp instanceof JPanel) { adjustContainer((JPanel) subComp, fontSize); @@ -460,9 +479,11 @@ public static void adjustContainer(Container parentCon, int fontSize) { } } - /** Adapt a JPopupMenu to the GUI scaling. Use after all menu items have been added. */ + /** + * Adapt a JPopupMenu to the GUI scaling. Use after all menu items have been added. + */ public static void scaleMenu(final JComponent popup) { - for (Component comp: popup.getComponents()) { + for (Component comp : popup.getComponents()) { if ((comp instanceof JMenuItem)) { comp.setFont(getScaledFont()); scaleJMenuItem((JMenuItem) comp); @@ -481,7 +502,6 @@ public static void scaleComp(JComponent comp, int fontSize) { } /** - * * @param currentMonitor The DisplayMode of the current monitor * @return the width of the screen taking into account display scaling */ @@ -492,7 +512,6 @@ public static int getScaledScreenWidth(DisplayMode currentMonitor) { } /** - * * @param currentMonitor The DisplayMode of the current monitor * @return The height of the screen taking into account display scaling */ @@ -503,7 +522,6 @@ public static int getScaledScreenHeight(DisplayMode currentMonitor) { } /** - * * @return The height of the screen taking into account display scaling */ public static Dimension getScaledScreenSize(Component component) { @@ -511,7 +529,6 @@ public static Dimension getScaledScreenSize(Component component) { } /** - * * @param currentMonitor The DisplayMode of the current monitor * @return The height of the screen taking into account display scaling */ @@ -525,7 +542,6 @@ public static Dimension getScaledScreenSize(DisplayMode currentMonitor) { } /** - * * @return an image with the same aspect ratio that fits within the given bounds, or the existing image if it already does */ public static Image constrainImageSize(Image image, ImageObserver observer, int maxWidth, int maxHeight) { @@ -537,25 +553,24 @@ public static Image constrainImageSize(Image image, ImageObserver observer, int } //choose resize that fits in bounds - double scaleW = maxWidth / (double)w; - double scaleH = maxHeight / (double)h; - if (scaleW < scaleH ) { - return ImageUtil.getScaledImage(image, maxWidth, (int)(h*scaleW)); + double scaleW = maxWidth / (double) w; + double scaleH = maxHeight / (double) h; + if (scaleW < scaleH) { + return ImageUtil.getScaledImage(image, maxWidth, (int) (h * scaleW)); } else { - return ImageUtil.getScaledImage(image, (int)(w*scaleH), maxHeight); + return ImageUtil.getScaledImage(image, (int) (w * scaleH), maxHeight); } } /** - * * @param multiResImageMap a collection of widths matched with corresponding image file path - * @param parent component + * @param parent component * @return a JLabel setup to the correct size to act as a splash screen */ public static JLabel createSplashComponent(TreeMap multiResImageMap, Component parent) { // Use the current monitor so we don't "overflow" computers whose primary // displays aren't as large as their secondary displays. - Dimension scaledMonitorSize = getScaledScreenSize( parent.getGraphicsConfiguration().getDevice().getDisplayMode()); + Dimension scaledMonitorSize = getScaledScreenSize(parent.getGraphicsConfiguration().getDevice().getDisplayMode()); Image imgSplash = parent.getToolkit().getImage(multiResImageMap.floorEntry(scaledMonitorSize.width).getValue()); // wait for splash image to load completely @@ -571,9 +586,8 @@ public static JLabel createSplashComponent(TreeMap multiResImag } /** - * * @param imgSplashFile path to an image on disk - * @param parent component + * @param parent component * @return a JLabel setup to the correct size to act as a splash screen */ public static JLabel createSplashComponent(String imgSplashFile, Component parent) { @@ -596,9 +610,8 @@ public static JLabel createSplashComponent(String imgSplashFile, Component paren } /** - * - * @param imgSplash an image - * @param observer An imageObserver + * @param imgSplash an image + * @param observer An imageObserver * @param scaledMonitorSize the dimensions of the monitor taking into account display scaling * @return a JLabel setup to the correct size to act as a splash screen */ @@ -638,7 +651,7 @@ public static void keepOnScreen(JFrame component) { Rectangle r = new Rectangle(scaledScreenSize); // center and size if out of bounds - if ( (pos.x < 0) || (pos.y < 0) || + if ((pos.x < 0) || (pos.y < 0) || (pos.x + size.width > scaledScreenSize.width) || (pos.y + size.height > scaledScreenSize.getHeight())) { component.setLocationRelativeTo(null); @@ -658,10 +671,12 @@ public static void setHighQualityRendering(Graphics graph) { ((Graphics2D) graph).setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); } - /** A specialized panel for the header of a section. */ + /** + * A specialized panel for the header of a section. + */ public static class Header extends JPanel { private static final long serialVersionUID = -6235772150005269143L; - + public Header(String text) { super(); setLayout(new GridLayout(1, 1, 0, 0)); @@ -671,8 +686,10 @@ public Header(String text) { setBackground(alternateTableBGColor()); } } - - /** A panel for the content of a subsection of the dialog. */ + + /** + * A panel for the content of a subsection of the dialog. + */ public static class Content extends JPanel { private static final long serialVersionUID = -6605053283642217306L; @@ -680,15 +697,17 @@ public Content(LayoutManager layout) { this(); setLayout(layout); } - + public Content() { super(); setBorder(BorderFactory.createEmptyBorder(8, 25, 5, 25)); setAlignmentX(Component.LEFT_ALIGNMENT); } } - - /** A panel for a subsection of the dialog, e.g. Minefields. */ + + /** + * A panel for a subsection of the dialog, e.g. Minefields. + */ public static class OptionPanel extends FixedYPanel { private static final long serialVersionUID = -7168700339882132428L; @@ -700,14 +719,16 @@ public OptionPanel(String header) { } } - /** A JPanel that does not stretch vertically beyond its preferred height. */ + /** + * A JPanel that does not stretch vertically beyond its preferred height. + */ public static class FixedYPanel extends JPanel { private static final long serialVersionUID = -8805710112708937089L; - + public FixedYPanel(LayoutManager layout) { super(layout); } - + public FixedYPanel() { super(); } @@ -717,15 +738,17 @@ public Dimension getMaximumSize() { return new Dimension(super.getMaximumSize().width, getPreferredSize().height); } } - - /** A JPanel that does not stretch horizontally beyond its preferred width. */ + + /** + * A JPanel that does not stretch horizontally beyond its preferred width. + */ public static class FixedXPanel extends JPanel { private static final long serialVersionUID = -4634244641653743910L; public FixedXPanel(LayoutManager layout) { super(layout); } - + public FixedXPanel() { super(); } @@ -735,10 +758,10 @@ public Dimension getMaximumSize() { return new Dimension(getPreferredSize().width, super.getMaximumSize().height); } } - - /** + + /** * A JLabel with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. + * of the parent dialog, not following the mouse. * Used in the player settings and planetary settings dialogs. */ public static class TipLabel extends JLabel { @@ -757,7 +780,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -771,10 +794,10 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A JButton with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. + * of the parent dialog, not following the mouse. * Used in the player settings and planetary settings dialogs. */ public static class TipButton extends JButton { @@ -789,7 +812,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -803,11 +826,11 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** - * A MMComboBox with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. + /** + * A MMComboBox with a specialized tooltip display. Displays the tooltip to the right side + *

+ * of the parent dialog, not following the mouse. * Used in the player settings dialog. */ public static class TipCombo extends MMComboBox { @@ -815,7 +838,7 @@ public static class TipCombo extends MMComboBox { public TipCombo(String name) { super(name); } - + public TipCombo(String name, E[] items) { super(name, items); } @@ -830,7 +853,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -844,28 +867,28 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A JList with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. + * of the parent dialog, not following the mouse. */ public static class TipList extends JList { - + public TipList() { super(); } - + public TipList(ListModel dataModel) { super(dataModel); } - + @Override public Point getToolTipLocation(MouseEvent event) { Window win = SwingUtilities.getWindowAncestor(this); Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -879,42 +902,42 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A JTextField with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. Can also display a hint text + * of the parent dialog, not following the mouse. Can also display a hint text * such as "..., ..." when empty. * Used in the player settings and planetary settings dialogs. */ public static class TipTextField extends JTextField { String hintText; - + public TipTextField(int n) { super(n); } - + public TipTextField(String text, int n) { super(text, n); } - + public TipTextField(int n, String hint) { this(n); prepareForHint(hint); - + } - + public TipTextField(String text, int n, String hint) { this(text, n); prepareForHint(hint); } - + private void prepareForHint(String hint) { hintText = hint; addFocusListener(l); updateHint(); } - + private void updateHint() { if (getText().isEmpty()) { setText(hintText); @@ -922,7 +945,7 @@ private void updateHint() { setCaretPosition(0); } } - + @Override public void setText(String t) { if ((t != null) && !t.isBlank()) { @@ -930,13 +953,13 @@ public void setText(String t) { } super.setText(t); } - + FocusListener l = new FocusListener() { @Override public void focusLost(FocusEvent e) { updateHint(); } - + @Override public void focusGained(FocusEvent e) { if (getText().equals(hintText)) { @@ -952,7 +975,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -966,17 +989,17 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A JPanel with a specialized tooltip display. Displays the tooltip to the right side - * of the parent dialog, not following the mouse. + * of the parent dialog, not following the mouse. */ public static class TipPanel extends JPanel { - + public TipPanel() { super(); } - + public TipPanel(LayoutManager lm) { super(lm); } @@ -987,7 +1010,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -1001,14 +1024,14 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A JSlider with a specialized tooltip display. Displays the tooltip to the right side - * of the parent window (dialog), not following the mouse. + * of the parent window (dialog), not following the mouse. * Implement the missing super constructors as necessary. */ public static class TipSlider extends JSlider { - + public TipSlider(int orientation, int min, int max, int value) { super(orientation, min, max, value); } @@ -1019,7 +1042,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -1033,13 +1056,13 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** + + /** * A MMToggleButton with a specialized tooltip display. Displays the tooltip to the right side - * of the parent window (dialog), not following the mouse. + * of the parent window (dialog), not following the mouse. */ public static class TipMMToggleButton extends MMToggleButton { - + public TipMMToggleButton(String text) { super(text); } @@ -1050,7 +1073,7 @@ public Point getToolTipLocation(MouseEvent event) { Point origin = SwingUtilities.convertPoint(this, 0, 0, win); return new Point(win.getWidth() - origin.x, 0); } - + @Override public JToolTip createToolTip() { JToolTip tip = super.createToolTip(); @@ -1064,55 +1087,55 @@ public void setToolTipText(String text) { super.setToolTipText(formatSideTooltip(text)); } } - - /** - * Completes the tooltip for a dialog using one of the TipXXX clasess, setting - * its width and adding HTML tags. + + /** + * Completes the tooltip for a dialog using one of the TipXXX clasess, setting + * its width and adding HTML tags. */ public static String formatSideTooltip(String text) { String result = "

" + text; return scaleStringForGUI(result); } - + /** * This is a specialized JPanel for use with a button bar at the bottom * of a dialog for when it's possible that the button bar has to wrap (is * wider than the dialog and needs to use two or more rows for the buttons). - * With a normal JPanel the wrapped buttons just disappear. + * With a normal JPanel the wrapped buttons just disappear. * This Panel tries to detect when wrapping occurs and then extends vertically. - * Note that it will only extend to two rows, not more. But if three rows of + * Note that it will only extend to two rows, not more. But if three rows of * buttons are used, this will be very obvious. - * The native FlowLayout should be kept for the buttons. + * The native FlowLayout should be kept for the buttons. */ public static class WrappingButtonPanel extends JPanel { private static final long serialVersionUID = -6966176665047676553L; @Override public Dimension getPreferredSize() { - int height = super.getPreferredSize().height; + int height = super.getPreferredSize().height; if (getSize().width < super.getPreferredSize().width) { height = height * 2; } return new Dimension(super.getPreferredSize().width, height); } - + @Override public Dimension getMinimumSize() { return new Dimension(super.getMinimumSize().width, getPreferredSize().height); } - + @Override public Dimension getMaximumSize() { return new Dimension(super.getMaximumSize().width, getPreferredSize().height); } } - + /** * Returns a single menu item with the given text, the given command string * cmd, the given enabled state, and assigned the given listener. */ - public static JMenuItem menuItem(String text, String cmd, boolean enabled, - ActionListener listener) { + public static JMenuItem menuItem(String text, String cmd, boolean enabled, + ActionListener listener) { return menuItem(text, cmd, enabled, listener, Integer.MIN_VALUE); } @@ -1122,8 +1145,8 @@ public static JMenuItem menuItem(String text, String cmd, boolean enabled, * cmd, the given enabled state, and assigned the given listener. Also assigns * the given key mnemonic. */ - public static JMenuItem menuItem(String text, String cmd, boolean enabled, - ActionListener listener, int mnemonic) { + public static JMenuItem menuItem(String text, String cmd, boolean enabled, + ActionListener listener, int mnemonic) { JMenuItem result = new JMenuItem(text); result.setActionCommand(cmd); @@ -1134,10 +1157,10 @@ public static JMenuItem menuItem(String text, String cmd, boolean enabled, } return result; } - - /** - * Returns a Font object using the "Dialog" logic font. The font size is based on - * size 14 and scaled with the current gui scaling. + + /** + * Returns a Font object using the "Dialog" logic font. The font size is based on + * size 14 and scaled with the current gui scaling. */ public static Font getScaledFont() { return new Font(MMConstants.FONT_DIALOG, Font.PLAIN, scaleForGUI(FONT_SCALE1)); @@ -1165,12 +1188,82 @@ public ScaledEmptyBorder(int top, int left, int bottom, int right) { } } - /** Returns true when a modal dialog such as the Camo Chooser or a Load Force dialog is currently shown. */ + /** + * Returns true when a modal dialog such as the Camo Chooser or a Load Force dialog is currently shown. + */ public static boolean isModalDialogDisplayed() { return Stream.of(Window.getWindows()) .anyMatch(w -> w.isShowing() && (w instanceof JDialog) && ((JDialog) w).isModal()); } + /** + * Opens a browser window if the OS supports it and shows the MUL unit entry for the given MUL ID. When + * mulid is 0 or less, this method does nothing. Other errors are logged. + * + * @param mulid The MUL ID to show; should be 1 or more + */ + public static void showUnitInMul(int mulid) { + showUnitInMul(mulid, null); + } + + /** + * Opens a browser window if the OS supports it and shows the MUL unit entry for the given MUL ID. When + * mulid is 0 or less, this method does nothing. When an exception occurs, an error dialog is shown with + * the given parentFrame as parent. + * + * @param mulid The MUL ID to show; should be 1 or more + * @param parentFrame The parent Component; used only for an error dialog + */ + public static void showUnitInMul(int mulid, JFrame parentFrame) { + if (mulid > 0) { + openInBrowser(MMConstants.MUL_URL_UNIT_PREFIX + mulid, parentFrame); + } + } + + /** + * Opens a browser window if the OS supports it and shows the MUL faction entry for the given MUL ID. When + * mulid is 0 or less, this method does nothing. Other errors are logged. + * + * @param mulid The MUL ID to show; should be 1 or more + */ + public static void showFactionInMul(int mulid) { + showFactionInMul(mulid, null); + } + + /** + * Opens a browser window if the OS supports it and shows the MUL faction entry for the given MUL ID. When + * mulid is 0 or less, this method does nothing. When an exception occurs, an error dialog is shown with + * the given parentFrame as parent. + * + * @param mulid The MUL ID to show; should be 1 or more + * @param parentFrame The parent Component; used only for an error dialog + */ + public static void showFactionInMul(int mulid, @Nullable JFrame parentFrame) { + if (mulid > 0) { + openInBrowser(MMConstants.MUL_URL_FACTION_PREFIX + mulid, parentFrame); + } + } + + /** + * Opens a browser window if the OS supports it and shows the given link. When an exception occurs and + * the given parentFrame is not null, an error dialog is shown. + * + * @param link The full link to show + * @param parentFrame The parent Component; used only for an error dialog + */ + public static void openInBrowser(String link, @Nullable JFrame parentFrame) { + try { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(new URL(link).toURI()); + } + } catch (Exception ex) { + LogManager.getLogger().error("", ex); + if (parentFrame != null) { + JOptionPane.showMessageDialog(parentFrame, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); + } + } + } + // PRIVATE private final static Color LIGHTUI_GREEN = new Color(20, 140, 20); diff --git a/megamek/src/megamek/common/MechView.java b/megamek/src/megamek/common/MechView.java index f64c3b35493..6df6aa0dfc7 100644 --- a/megamek/src/megamek/common/MechView.java +++ b/megamek/src/megamek/common/MechView.java @@ -251,7 +251,7 @@ public MechView(final Entity entity, final boolean showDetail, final boolean use sHead.add(new LabeledElement(Messages.getString("MechView.Cost"), dFormatter.format(cost) + " C-bills")); if (entity.hasMulId()) { - sHead.add(new HyperLinkElement(MMConstants.MUL_URL_PREFIX + entity.getMulId(), + sHead.add(new HyperLinkElement(MMConstants.MUL_URL_UNIT_PREFIX + entity.getMulId(), Messages.getString("MechView.Source"))); if (!entity.getSource().isEmpty()) { sHead.add(new LabeledElement("", entity.getSource())); diff --git a/megamek/src/megamek/utilities/RATGeneratorEditor.java b/megamek/src/megamek/utilities/RATGeneratorEditor.java index f13d698a079..b9b15f8ca19 100644 --- a/megamek/src/megamek/utilities/RATGeneratorEditor.java +++ b/megamek/src/megamek/utilities/RATGeneratorEditor.java @@ -13,9 +13,9 @@ */ package megamek.utilities; -import megamek.MMConstants; import megamek.client.ratgenerator.*; import megamek.client.ratgenerator.FactionRecord.TechCategory; +import megamek.client.ui.swing.util.UIUtil; import megamek.client.ui.swing.util.UIUtil.FixedXPanel; import megamek.client.ui.swing.util.UIUtil.FixedYPanel; import megamek.common.Configuration; @@ -34,7 +34,6 @@ import javax.swing.table.TableRowSorter; import java.awt.*; import java.io.File; -import java.net.URL; import java.util.List; import java.util.*; import java.util.stream.Collectors; @@ -78,6 +77,8 @@ public class RATGeneratorEditor extends JFrame { private final JTextField txtNewFaction = new JTextField(20); private final JCheckBox chkShowSubfactions = new JCheckBox(); private final JCheckBox chkShowMinorFactions = new JCheckBox(); + private final JButton butFactionMUL = new JButton("Open MUL"); + private int currentFactionMulId = -1; private final JTable tblMasterFactionList = new JTable(); private FactionListTableModel masterFactionListModel; @@ -150,6 +151,8 @@ private void initUI() { add(panMain, BorderLayout.CENTER); add(buildOptionPanel(), BorderLayout.PAGE_START); add(panButtons, BorderLayout.PAGE_END); + + setLocationRelativeTo(null); } private void loadAltDir() { @@ -265,7 +268,7 @@ public void removeUpdate(DocumentEvent evt) { }); unitSearchPanel.add(Box.createHorizontalStrut(15)); unitSearchPanel.add(butMUL); - butMUL.addActionListener(e -> showMUL()); + butMUL.addActionListener(e -> UIUtil.showUnitInMul(currentMulId, this)); tblMasterUnitList.setModel(masterUnitListModel); masterUnitListSorter = new TableRowSorter<>(masterUnitListModel); @@ -309,18 +312,6 @@ public void removeUpdate(DocumentEvent evt) { return new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, unitContainer, factionEditSide); } - private void showMUL() { - try { - if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE) - && currentMulId > 0) { - Desktop.getDesktop().browse(new URL(MMConstants.MUL_URL_PREFIX + currentMulId).toURI()); - } - } catch (Exception ex) { - LogManager.getLogger().error("", ex); - JOptionPane.showMessageDialog(this, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); - } - } - private JComponent createCopyBetweenButtonPanel() { JPanel panel = new JPanel(); JButton copyToModelButton = new JButton("\u25B2"); @@ -517,6 +508,8 @@ private JPanel createFactionTab() { chkShowMinorFactions.setSelected(true); chkShowMinorFactions.addActionListener(ev -> filterFactionList()); + butFactionMUL.addActionListener(e -> UIUtil.showFactionInMul(currentFactionMulId, this)); + masterFactionListModel = new FactionListTableModel(rg.getFactionList()); tblMasterFactionList.setModel(masterFactionListModel); masterFactionListSorter = new TableRowSorter<>(masterFactionListModel); @@ -533,10 +526,13 @@ private JPanel createFactionTab() { getFactionRecord(tblMasterFactionList.convertRowIndexToModel(tblMasterFactionList.getSelectedRow())); factionEditorModel.setData(rec); salvageEditorModel.setData(rec); + currentFactionMulId = rec.getMulId(); } else { factionEditorModel.clearData(); salvageEditorModel.clearData(); + currentFactionMulId = -1; } + butFactionMUL.setEnabled(currentFactionMulId > 0); }); JScrollPane masterScroll = new JScrollPane(tblMasterFactionList); JPanel masterPanel = new FixedXPanel(); @@ -581,6 +577,7 @@ private JPanel createFactionTab() { optionsPanel.add(copyButton); optionsPanel.add(chkShowSubfactions); optionsPanel.add(chkShowMinorFactions); + optionsPanel.add(butFactionMUL); JPanel salvageButtonPanel = new FixedYPanel(new FlowLayout(FlowLayout.LEFT)); salvageButtonPanel.add(txtSalvageFaction); @@ -1094,10 +1091,11 @@ private class FactionListTableModel extends DefaultTableModel { public static final int COL_PERIPHERY = 5; public static final int COL_RATINGS = 6; public static final int COL_USE_ALT_FACTION = 7; - public static final int NUM_COLS = 8; + public static final int COL_MULID = 8; + public static final int NUM_COLS = 9; public final String[] colNames = {"Code", "Name", "Years", "Minor", "Clan", - "Periphery", "Ratings", "Use Alt"}; + "Periphery", "Ratings", "Use Alt", "MUL ID"}; private final ArrayList data; @@ -1146,8 +1144,11 @@ public boolean isCellEditable(int row, int col) { public Class getColumnClass(int col) { if (col == COL_MINOR || col == COL_CLAN || col == COL_PERIPHERY) { return Boolean.class; + } else if (col == COL_MULID) { + return String.class; + } else { + return String.class; } - return String.class; } @Override @@ -1157,6 +1158,8 @@ public Object getValueAt(int row, int col) { return data.get(row).getKey(); case COL_NAME: return data.get(row).getNamesAsString(); + case COL_MULID: + return data.get(row).getMulIdsAsString(); case COL_YEARS: return data.get(row).getYearsAsString(); case COL_MINOR: @@ -1186,6 +1189,10 @@ public void setValueAt(Object val, int row, int col) { case COL_NAME: data.get(row).setNames((String) val); break; + case COL_MULID: + data.get(row).setMulIds((String) val); + currentFactionMulId = data.get(row).getMulId(); + break; case COL_YEARS: try { data.get(row).setYears((String) val); @@ -1229,7 +1236,7 @@ private static class FactionEditorTableModel extends DefaultTableModel { private static final int[] WEIGHT_DIST_UNIT_TYPES = { UnitType.MEK, UnitType.TANK, UnitType.AERO }; - + private FactionRecord factionRec; public FactionEditorTableModel(FactionRecord rec) { @@ -1287,7 +1294,7 @@ public Object getValueAt(int row, int col) { if (row == 0) { return "Salvage %"; } else if (row > factionRec.getRatingLevels().size() * CATEGORIES.length) { - return WEIGHT_DIST_UNIT_TYPES[row - 1 - factionRec.getRatingLevels().size() * CATEGORIES.length]; + return UnitType.getTypeName(WEIGHT_DIST_UNIT_TYPES[row - 1 - factionRec.getRatingLevels().size() * CATEGORIES.length]); } else { return CATEGORIES[(row - 1) / factionRec.getRatingLevels().size()] + " " + factionRec.getRatingLevels().get((row - 1) % factionRec.getRatingLevels().size());