├── .gitignore ├── LICENSE ├── README.md ├── documentation ├── Continents.png ├── screenshot-add-mods.png ├── screenshot.png └── screenshot2.png ├── external-resources ├── config.properties ├── mod-repository │ └── mods-db.txt └── mods.txt ├── external-test-resources ├── Community │ ├── 1rl │ │ └── manifest.json │ ├── BroughtyCastle │ │ └── manifest.json │ ├── airplane-model │ │ └── manifest.json │ ├── airport-narsarsuaq │ │ └── manifest.json │ ├── city-tallinn │ │ └── manifest.json │ ├── landmark-amsterdam │ │ └── manifest.json │ ├── landmark-birmingham │ │ └── manifest.json │ ├── landmark-london1 │ │ └── manifest.json │ ├── landmark-london2 │ │ └── manifest.json │ ├── myAirport │ │ └── manifest.json │ ├── myRomanianAirport │ │ └── manifest.json │ ├── mySimpleAirport │ │ └── manifest.json │ └── mySimpleAirport2 │ │ └── manifest.json ├── config.properties ├── labels.properties └── mods.txt ├── pom.xml └── src └── main ├── java └── msfsmodmanager │ ├── Main.groovy │ ├── ex │ └── ModsParseException.groovy │ ├── logic │ ├── ErrorHandler.groovy │ ├── ModActivator.groovy │ ├── ModChecker.groovy │ ├── ModDeleter.groovy │ └── ModsDbHandler.groovy │ ├── model │ ├── AircraftType.groovy │ ├── Cities.groovy │ ├── Continent.groovy │ ├── FirstDefinition.groovy │ ├── Mod.groovy │ ├── ModType.groovy │ └── Selection.groovy │ ├── state │ ├── Config.groovy │ ├── FileSystem.groovy │ ├── MasterData.groovy │ ├── Mods.groovy │ └── ModsDb.groovy │ ├── ui │ ├── Dialogs.groovy │ ├── I18N.groovy │ ├── edit │ │ ├── ColoredListCellRenderer.java │ │ ├── EditModsFrame.form │ │ ├── EditModsFrame.java │ │ ├── EditModsFrameHandler.groovy │ │ ├── ExampleComboBox.groovy │ │ ├── ExampleTextField.java │ │ ├── HasText.java │ │ ├── MessageLabel.java │ │ ├── MessagePanel.groovy │ │ └── WarnLightLabel.java │ ├── error │ │ ├── ErrorFrame.form │ │ ├── ErrorFrame.java │ │ └── ErrorFrameHandler.groovy │ ├── git │ │ ├── GitFrame.form │ │ └── GitFrame.java │ └── main │ │ ├── AircraftPanel.form │ │ ├── AircraftPanel.java │ │ ├── CheckBoxTitledBorder.java │ │ ├── CheckBoxTitledBorderPanel.form │ │ ├── CheckBoxTitledBorderPanel.java │ │ ├── ContinentPanel.java │ │ ├── CountryPanel.java │ │ ├── MainFrame.form │ │ ├── MainFrame.java │ │ ├── MainFrameHandler.groovy │ │ ├── SelectableCheckBox.java │ │ ├── SelectableComp.java │ │ └── SelectableComps.groovy │ └── util │ ├── Browser.groovy │ ├── CmdLine.groovy │ ├── Git.groovy │ ├── StringUtil.groovy │ ├── SwingUtil.groovy │ └── WebReader.groovy └── resources └── default_labels.properties /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 11 | .mvn/wrapper/maven-wrapper.jar 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, captn-nick 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /documentation/Continents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/documentation/Continents.png -------------------------------------------------------------------------------- /documentation/screenshot-add-mods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/documentation/screenshot-add-mods.png -------------------------------------------------------------------------------- /documentation/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/documentation/screenshot.png -------------------------------------------------------------------------------- /documentation/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/documentation/screenshot2.png -------------------------------------------------------------------------------- /external-resources/config.properties: -------------------------------------------------------------------------------- 1 | path.community= 2 | path.temp= 3 | 4 | ModsDb.updateWithGit=false 5 | 6 | ModType.defaultSelection=AP, LM, LF, LI 7 | 8 | Continent.EU.Countries=GBR, Sco, IRL, DEU, FRA, ESP, ITA, GRC, NLD, BEL, NOR, SWE, CHE 9 | Continent.UC.Countries=CAN, CA, NY, TX, FL, AK 10 | Continent.MA.Countries=MEX 11 | Continent.SA.Countries=BRA 12 | Continent.AF.Countries=ZAF, EGY, TUN 13 | Continent.AS.Countries=RUS, IND, CHN, JPN 14 | Continent.OC.Countries=AUS, NZL 15 | Continent.AA.Countries= 16 | Continent.OF.Countries= 17 | 18 | Country.NLD.showCities=false 19 | Country.BEL.showCities=false 20 | Country.CHE.showCities=false 21 | 22 | Cities.minMods=1 -------------------------------------------------------------------------------- /external-resources/mods.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-resources/mods.txt -------------------------------------------------------------------------------- /external-test-resources/Community/1rl/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/1rl/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/BroughtyCastle/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/BroughtyCastle/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/airplane-model/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/airplane-model/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/airport-narsarsuaq/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/airport-narsarsuaq/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/city-tallinn/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/city-tallinn/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/landmark-amsterdam/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/landmark-amsterdam/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/landmark-birmingham/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/landmark-birmingham/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/landmark-london1/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/landmark-london1/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/landmark-london2/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/landmark-london2/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/myAirport/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/myAirport/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/myRomanianAirport/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/myRomanianAirport/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/mySimpleAirport/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/mySimpleAirport/manifest.json -------------------------------------------------------------------------------- /external-test-resources/Community/mySimpleAirport2/manifest.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Selector/91c4255830d8f0d4550cc88870ddc08621271313/external-test-resources/Community/mySimpleAirport2/manifest.json -------------------------------------------------------------------------------- /external-test-resources/config.properties: -------------------------------------------------------------------------------- 1 | path.community= 2 | path.temp= 3 | 4 | ModsDb.updateWithGit=false 5 | 6 | ModType.defaultSelection=AP, LM, LF, LI 7 | 8 | Continent.EU.Countries=GBR, Sco, IRL, DEU, FRA, ESP, ITA, GRC, NLD, BEL, NOR, SWE, CHE 9 | Continent.UC.Countries=CAN, CA, NY, TX, FL, AK 10 | Continent.MA.Countries=MEX 11 | Continent.SA.Countries=BRA 12 | Continent.AF.Countries=ZAF, EGY, TUN 13 | Continent.AS.Countries=RUS, IND, CHN, JPN 14 | Continent.OC.Countries=AUS, NZL 15 | Continent.AA.Countries= 16 | Continent.OF.Countries=STW 17 | 18 | Country.NLD.showCities=false 19 | Country.BEL.showCities=false 20 | Country.CHE.showCities=false 21 | 22 | Cities.minMods=1 -------------------------------------------------------------------------------- /external-test-resources/labels.properties: -------------------------------------------------------------------------------- 1 | Country.DEU=Deutschland 2 | 3 | Country.STW=Star Wars -------------------------------------------------------------------------------- /external-test-resources/mods.txt: -------------------------------------------------------------------------------- 1 | AP EU GBR mySimpleAirport - ## A simple example Airport! myself https://www.flightsim.to/file/mySimpleAirport 2 | AP EU GBR myAirport ABC1 ## An example Airport! myself https://www.flightsim.to/file/myAirport 3 | AP EU ROU myRomanianAirport ASDF ## An airport in Romania myself https://www.flightsim.to/file/myRomanianAirport 4 | LM EU GBR landmark-london1 London ## An example Landmark in London! someone else https://www.flightsim.to/file/landmark-london1 5 | LM EU GBR landmark-london2 London ## A 2nd example Landmark in London! someone else https://www.flightsim.to/file/landmark-london2 6 | LM EU GBR landmark-birmingham Birmingham ## A landmark in Birmingham, GBR someone else https://www.flightsim.to/file/landmark-birmingham 7 | LM EU NLD landmark-amsterdam Amsterdam ## A landmark in Amsterdam! someone else https://www.flightsim.to/file/landmark-amsterdam 8 | CT EU city-tallinn Tallinn ## A city in Europe someone else https://www.flightsim.to/file/city-tallinn 9 | AM airplane-model AL ## An airplane model someone else https://www.flightsim.to/file/airplane-model 10 | AP AA GRL airport-narsarsuaq BGBW ## BGBW Narsarsuaq Greenland thedude https://www.flightsim.to/file/734/bgbw-narsarsuaq-greenland -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | msfsmodmanager 5 | MsfsModSelector 6 | 1.0-SNAPSHOT 7 | jar 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-compiler-plugin 13 | 3.3 14 | 15 | 16 | org.codehaus.groovy 17 | groovy-eclipse-compiler 18 | 2.9.2-01 19 | 20 | 21 | org.codehaus.groovy 22 | groovy-eclipse-batch 23 | 2.4.3-01 24 | 25 | 26 | 27 | groovy-eclipse-compiler 28 | 29 | 30 | 31 | org.codehaus.groovy 32 | groovy-eclipse-compiler 33 | 2.9.2-01 34 | true 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-assembly-plugin 39 | 2.4.1 40 | 41 | 42 | 43 | jar-with-dependencies 44 | 45 | 46 | 47 | 48 | msfsmodmanager.Main 49 | 50 | 51 | 52 | 53 | 54 | 55 | make-assembly 56 | 57 | package 58 | 59 | single 60 | 61 | 62 | 63 | 64 | 65 | maven-resources-plugin 66 | 3.0.1 67 | 68 | 69 | copy-resources 70 | validate 71 | 72 | copy-resources 73 | 74 | 75 | ${basedir}/target/ 76 | 77 | 78 | external-resources 79 | true 80 | 81 | 82 | external-test-resources 83 | true 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.codehaus.mojo 92 | exec-maven-plugin 93 | 1.3.2 94 | 95 | 96 | verify 97 | 98 | exec 99 | 100 | 101 | 102 | 103 | java 104 | 105 | -jar 106 | target/MsfsModSelector-1.0-SNAPSHOT-jar-with-dependencies.jar 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | org.codehaus.groovy 115 | groovy-all 116 | 2.4.5 117 | 118 | 119 | org.swinglabs.swingx 120 | swingx-autocomplete 121 | 1.6.5-1 122 | 123 | 124 | 125 | UTF-8 126 | 1.8 127 | 1.8 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/Main.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.ex.ModsParseException 5 | import msfsmodmanager.logic.ErrorHandler 6 | import msfsmodmanager.logic.ModChecker 7 | import msfsmodmanager.model.* 8 | import msfsmodmanager.state.* 9 | import msfsmodmanager.ui.Dialogs 10 | import msfsmodmanager.ui.main.MainFrame 11 | import msfsmodmanager.util.CmdLine 12 | import msfsmodmanager.util.WebReader 13 | 14 | @CompileStatic 15 | class Main { 16 | public static boolean FIRST_START = false 17 | 18 | public static void main(String[] args) { 19 | Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { 20 | public void uncaughtException(Thread t, Throwable e) { 21 | ErrorHandler.handleGlobalError(e); 22 | } 23 | }); 24 | 25 | try { 26 | start(args) 27 | } 28 | catch(Exception ex) { 29 | ErrorHandler.handleGlobalError(ex) 30 | } 31 | } 32 | 33 | private static void start(String[] args) { 34 | if ("--dontShowErrorPopups" in args) { 35 | ErrorHandler.showErrorsAsPopups = false 36 | } 37 | 38 | restart() 39 | } 40 | 41 | public static void restart() { 42 | if (init()) { 43 | MainFrame.main() 44 | } 45 | 46 | FIRST_START = false 47 | } 48 | 49 | public static boolean init() { 50 | FileSystem.init() 51 | 52 | if (FIRST_START) { 53 | if (Dialogs.updateModsDb()) { 54 | FIRST_START = false 55 | if (ModsDb.instance.update() { 56 | restart() 57 | }) { 58 | if (!ModsDb.UPDATE_WITH_GIT) { 59 | Dialogs.updateModsDbSuccessful() 60 | } 61 | else { 62 | return // ModsDb.instance.update() will take control. 63 | } 64 | } 65 | else { 66 | return 67 | } 68 | } 69 | } 70 | 71 | Mods.instance.loadRegisteredMods() 72 | ModsDb.instance.loadRegisteredMods() 73 | 74 | MasterData.init() 75 | 76 | Continent.loadContinents() 77 | Cities.loadCities() 78 | 79 | List allMods = Mods.instance.findAllMods() 80 | boolean ret = ( 81 | checkUnregisteredAndCorruptedMods(allMods) && 82 | checkDuplicatedMods(allMods) && 83 | checkUnregisteredMods(allMods) && 84 | checkUninstalledMods() && 85 | checkCorruptedMods() 86 | ) 87 | return ret 88 | } 89 | 90 | private static boolean checkUnregisteredAndCorruptedMods(List allMods) { 91 | List modFiles = ModChecker.findUnregisteredAndCorruptedMods(allMods) 92 | if (!modFiles.empty) { 93 | return ErrorHandler.error( 94 | "Error.010", 95 | "Found mods which weren't registered and have a corrupted directory structure:", 96 | modFiles*.name.join("\n") 97 | ) 98 | } 99 | return true 100 | } 101 | 102 | private static boolean checkDuplicatedMods(List allMods) { 103 | List modNames = ModChecker.findDuplicatedMods(allMods) 104 | if (!modNames.empty) { 105 | return ErrorHandler.error( 106 | "Error.020", 107 | "Found mods which are duplicated (one in Community, one in Temp folder):", 108 | modNames.join("\n"), 109 | null, 110 | ErrorHandler.ErrorType.DUPLICATE_MODS 111 | ) 112 | } 113 | return true 114 | } 115 | 116 | private static boolean checkUnregisteredMods(List allMods) { 117 | List modNames = ModChecker.findUnregisteredMods(allMods) 118 | ModsDb.DbInformationFound informationFromDb = ModsDb.instance.replaceWithDbInformation(modNames) 119 | 120 | if (!modNames.empty) { 121 | return ErrorHandler.unregisteredModsFoundError(informationFromDb.lines) 122 | } 123 | return true 124 | } 125 | 126 | private static boolean checkUninstalledMods() { 127 | List modNames = ModChecker.findUninstalledMods() 128 | if (!modNames.empty) { 129 | return ErrorHandler.error ( 130 | "Error.040", 131 | "Found mods which were registered but aren't present in mod directory:", 132 | modNames.join("\n") 133 | ) 134 | } 135 | return true 136 | } 137 | 138 | private static boolean checkCorruptedMods() { 139 | List mods = ModChecker.findCorruptedMods() 140 | if (!mods.empty) { 141 | return ErrorHandler.error ( 142 | "Error.050", 143 | "Found mods with corrupted directory structure:", 144 | mods.collect { 145 | it.name + (it.active ? "" : " (deactivated)") 146 | }.join("\n") 147 | ) 148 | } 149 | return true 150 | } 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ex/ModsParseException.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ex 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.TupleConstructor 5 | import msfsmodmanager.model.* 6 | 7 | @CompileStatic 8 | abstract class ModsParseException extends RuntimeException { 9 | String errorNo 10 | boolean showStackTrace 11 | 12 | String info 13 | 14 | public ModsParseException(String errorNo, boolean showStackTrace, String info, Exception cause) { 15 | super(cause) 16 | this.errorNo = errorNo 17 | this.showStackTrace = showStackTrace 18 | 19 | this.info = info 20 | } 21 | 22 | public String getMessage() { 23 | return info 24 | } 25 | 26 | public static class LineParseException extends ModsParseException { 27 | String line 28 | int lineNo 29 | 30 | public LineParseException(String errorNo="100", boolean showStackTrace=true, 31 | String fileName, String line, int lineNo, String info="Cannot read line ${lineNo} in ${fileName}.", Exception cause) { 32 | super(errorNo, showStackTrace, info, cause) 33 | this.line = line 34 | this.lineNo = lineNo 35 | } 36 | 37 | public String getMessage() { 38 | return info + "\n" + line 39 | } 40 | } 41 | 42 | public static class MultipleModsCheckException extends ModsParseException { 43 | Map checkErrorsByMod 44 | 45 | public MultipleModsCheckException(Map checkErrorsByMod) { 46 | super("130", true, /Errors found in multiple mods while reading them./, { 47 | try { checkErrorsByMod.entrySet().iterator().next().value.throwSubException() } catch (Exception ex) { return ex } 48 | }() as Exception) 49 | this.checkErrorsByMod = checkErrorsByMod 50 | } 51 | } 52 | 53 | public static class ModCheckException extends LineParseException { 54 | Mod.CheckErrors checkErrors 55 | 56 | public ModCheckException(String fileName, Mod.CheckErrors checkErrors) { 57 | super("131", true, fileName, null, -1, /Multiple errors found when reading lines in ${fileName}./, { 58 | try { checkErrors.throwSubException() } catch (Exception ex) { return ex } 59 | }() as Exception) 60 | 61 | this.checkErrors = checkErrors 62 | } 63 | } 64 | 65 | public static class MissingDataForLineParseException extends LineParseException { 66 | public MissingDataForLineParseException(String fileName, String information, String line, int lineNo) { 67 | super("101", true, fileName, line, lineNo, /Cannot read "$information" information for line ${lineNo} in ${fileName}:/, null) 68 | } 69 | } 70 | 71 | public static class DuplicateModNameForLineParseException extends LineParseException { 72 | public DuplicateModNameForLineParseException(String fileName, FirstDefinition first, String conflictingLine, int lineNo) { 73 | super("110", false, fileName, extractLineInformation(first, conflictingLine, lineNo), lineNo, /Duplicate mod definition found in ${fileName}:/, null) 74 | } 75 | } 76 | 77 | public static class ConflictingDataForLineParseException extends LineParseException { 78 | public ConflictingDataForLineParseException(String fileName, String information, FirstDefinition first, String conflictingLine, int lineNo) { 79 | super("111", false, fileName, extractLineInformation(first, conflictingLine, lineNo), lineNo, /Inconsistent "$information" information found in ${fileName}:/, null) 80 | } 81 | } 82 | 83 | private static String extractLineInformation(FirstDefinition first, String conflictingLine, int lineNo) { 84 | return """- Original definition in line ${first.lineNo}: 85 | ${first.line} 86 | 87 | - Conflicting definition in line ${lineNo}: 88 | ${conflictingLine}""" 89 | } 90 | 91 | public static class IllegalDataForLineParseException extends LineParseException { 92 | public IllegalDataForLineParseException(String fileName, String information, String value, String line, int lineNo) { 93 | super("120", false, fileName, line, lineNo, /"$value" is not accepted as "$information" information for line ${lineNo} in ${fileName}:/, null) 94 | } 95 | } 96 | 97 | public static class IllegalContinentDataForLineParseException extends LineParseException { 98 | public IllegalContinentDataForLineParseException(String fileName, String continent, String correctContinent, String country, String line, int lineNo) { 99 | super("120", false, fileName, line, lineNo, /"$continent" is not the correct continent for country "$country", should be "$correctContinent" for line ${lineNo} in ${fileName}:/, null) 100 | } 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/logic/ErrorHandler.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.logic 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import msfsmodmanager.ex.ModsParseException 6 | import msfsmodmanager.state.Mods 7 | import msfsmodmanager.ui.edit.EditModsFrame 8 | import msfsmodmanager.util.StringUtil 9 | import msfsmodmanager.ui.error.ErrorFrame 10 | 11 | @CompileStatic 12 | class ErrorHandler { 13 | public static boolean showErrorsAsPopups = true 14 | 15 | public static enum ErrorType { 16 | DEFAULT, 17 | DUPLICATE_MODS, 18 | UNREGISTERED_MODS, 19 | UNREGISTERED_MODS_IN_DB, 20 | } 21 | 22 | public static void handleGlobalError(Throwable ex) { 23 | handleGlobalError(ex, true) 24 | } 25 | 26 | public static void handleGlobalError(Throwable ex, boolean forceShutdown) { 27 | if (ex in ModsParseException.LineParseException) { 28 | ModsParseException.LineParseException lpex = (ModsParseException.LineParseException)ex 29 | 30 | error ( 31 | "Error.${lpex.errorNo}", 32 | lpex.info, 33 | lpex.line, 34 | lpex.showStackTrace ? StringUtil.toString(lpex) : null, 35 | forceShutdown 36 | ) 37 | } 38 | else { 39 | error ( 40 | "Error.001", 41 | "Unexpected error occurred.", 42 | null, 43 | StringUtil.toString(ex), 44 | forceShutdown 45 | ) 46 | } 47 | } 48 | 49 | public static boolean unregisteredModsFoundError(List lines) { 50 | EditModsFrame.show( lines.collect { Mods.parseSafeLine(it) } ) 51 | } 52 | 53 | public static boolean error(String title, String message, String details, String stackTrace=null, 54 | ErrorType errorType=ErrorType.DEFAULT) { 55 | return error(title, message, details, stackTrace, errorType, true) 56 | } 57 | 58 | public static boolean error(String title, String message, String details, String stackTrace=null, 59 | ErrorType errorType=ErrorType.DEFAULT, boolean forceShutdown) { 60 | println title 61 | println "!!! " + message 62 | if (details) println details 63 | 64 | if (stackTrace) { 65 | println """If you believe this is a bug, please report it on the project's GitHub page (https://github.com/captn-nick/MSFS-Mod-Selector/issues) 66 | with the following information. Make sure to read the README (https://github.com/captn-nick/MSFS-Mod-Selector) first.""" 67 | println stackTrace 68 | } 69 | if (showErrorsAsPopups) { 70 | ErrorFrame.show(title, message, details, stackTrace, errorType, forceShutdown) 71 | } 72 | else { 73 | if (forceShutdown) { 74 | System.exit(1) 75 | } 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/logic/ModActivator.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.model.* 5 | import msfsmodmanager.state.* 6 | 7 | @CompileStatic 8 | class ModActivator { 9 | public static void activateMods(Selection selection) { 10 | List activeMods = Mods.instance.mods.findAll { mod -> 11 | selection.activates(mod) 12 | } 13 | 14 | List inactiveMods = Mods.instance.mods - activeMods 15 | 16 | activeMods.each { mod -> 17 | FileSystem.activate(mod) 18 | mod.active = true 19 | } 20 | 21 | inactiveMods.each { mod -> 22 | FileSystem.deactivate(mod) 23 | mod.active = false 24 | } 25 | 26 | println "Mods activated:" 27 | println activeMods.join("\n") 28 | println activeMods.size() + " of " + Mods.instance.mods.size() + " in total." 29 | println() 30 | } 31 | 32 | public static void deactivateAllMods() { 33 | Mods.instance.mods.each { mod -> 34 | FileSystem.deactivate(mod) 35 | mod.active = false 36 | } 37 | 38 | println "All mods deactivated.\n" 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/logic/ModChecker.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.model.Mod 5 | import msfsmodmanager.state.Mods 6 | 7 | @CompileStatic 8 | class ModChecker { 9 | public static List findUnregisteredAndCorruptedMods(List allMods) { 10 | return allMods.findAll { 11 | !(new File(it.path + /\manifest.json/).exists()) 12 | } 13 | } 14 | 15 | public static List findDuplicatedMods(List allMods) { 16 | return allMods.findAll { mod -> 17 | allMods.findAll{ it.name == mod.name }.size() == 2 18 | }*.name.unique() 19 | } 20 | 21 | public static List findUnregisteredMods(List allMods) { 22 | return allMods.findAll { 23 | !(it.name in Mods.instance.mods*.name) 24 | }*.name 25 | } 26 | 27 | public static List findUninstalledMods() { 28 | return Mods.instance.mods.findAll { 29 | !(new File(it.file.path).exists()) 30 | }*.name 31 | } 32 | 33 | public static List findCorruptedMods() { 34 | return Mods.instance.mods.findAll { 35 | !(new File(it.file.path + /\manifest.json/).exists()) 36 | } 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/logic/ModDeleter.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.logic 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import msfsmodmanager.state.FileSystem 6 | 7 | @CompileStatic 8 | class ModDeleter { 9 | public static void deleteInTempDirectory(List modNames) { 10 | modNames.each { String name -> 11 | new File(FileSystem.TEMP_DIR + $/\/$ + name).deleteDir() 12 | } 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/logic/ModsDbHandler.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.model.Mod 5 | import msfsmodmanager.state.Mods 6 | import msfsmodmanager.state.ModsDb 7 | import msfsmodmanager.util.Git 8 | import msfsmodmanager.util.WebReader 9 | 10 | @CompileStatic 11 | class ModsDbHandler { 12 | public static void addUserDefinedModsToModsDb() { 13 | Map mods = Mods.instance.getUserDefinedMods() 14 | ModsDb.instance.addAllMods(mods) 15 | ModsDb.instance.saveToTxt() 16 | } 17 | 18 | public static void updateWithHttp() { 19 | String text = WebReader.readUrl(ModsDb.HTTP_UPDATE_URL) 20 | 21 | ModsDb.instance.modInfoFile.setText(text, "UTF-8") 22 | } 23 | 24 | public static boolean updateWithGit(Closure okButtonAction) { 25 | return Git.pull(ModsDb.instance.modInfoFile.parentFile.absolutePath, okButtonAction) 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/AircraftType.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.TupleConstructor 5 | import msfsmodmanager.ui.I18N 6 | 7 | @CompileStatic 8 | @TupleConstructor 9 | enum AircraftType { 10 | AIRLINER("AL"), 11 | JET("JT"), 12 | TURBOPROP("TP"), 13 | PROPELLER("PR"), 14 | 15 | String abbr 16 | 17 | public String toTxt() { 18 | return abbr 19 | } 20 | 21 | public static AircraftType parse(String abbr) { 22 | return values().find { it.abbr == abbr } 23 | } 24 | 25 | public String toString() { 26 | return I18N.getString("AircraftType.${abbr}") 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/Cities.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.state.Mods 5 | import msfsmodmanager.state.Config 6 | import msfsmodmanager.ex.ModsParseException 7 | 8 | @CompileStatic 9 | class Cities { 10 | private static Map> BY_COUNTRY = [:].withDefault {new TreeSet()} 11 | private static Map> BY_CONTINENT = [:].withDefault {new TreeSet()} 12 | public static Set DISABLED_CITIES = [] as Set 13 | 14 | public static Set byCountry(String country) { 15 | return BY_COUNTRY[country] - DISABLED_CITIES 16 | } 17 | 18 | public static Set byContinent(Continent continent) { 19 | return BY_CONTINENT[continent] - DISABLED_CITIES 20 | } 21 | 22 | public static loadCities() { 23 | Map numberOfMods = [:].withDefault { 0 } 24 | 25 | Mods.instance.mods.each { 26 | if (it.city) { 27 | numberOfMods[it.city] = numberOfMods[it.city] + 1 28 | 29 | if (it.country && Continent.belongsToAnyContinent(it.country)) { 30 | if (showCitiesFor(it.country)) { 31 | BY_COUNTRY[it.country] << it.city 32 | } 33 | else { 34 | DISABLED_CITIES << it.city 35 | } 36 | } 37 | else if (it.continent) { 38 | BY_CONTINENT[it.continent] << it.city 39 | } 40 | } 41 | } 42 | 43 | numberOfMods.each { k, v -> 44 | if (!showCitiesFor(v)) { 45 | DISABLED_CITIES << k 46 | } 47 | } 48 | } 49 | 50 | private static showCitiesFor(String country) { 51 | String ret = Config.getStringOrNull("Country.${country}.showCities") 52 | return ret == null || ret == "true" 53 | } 54 | 55 | private static boolean showCitiesFor(int occurrences) { 56 | return occurrences >= (Config.getString("Cities.minMods") as int) 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/Continent.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.ex.ModsParseException 5 | import msfsmodmanager.state.Config 6 | import msfsmodmanager.ui.I18N 7 | 8 | @CompileStatic 9 | class Continent { 10 | public static Continent EU = new Continent("EU") 11 | public static Continent UC = new Continent("UC") 12 | public static Continent MA = new Continent("MA") 13 | public static Continent SA = new Continent("SA") 14 | public static Continent AF = new Continent("AF") 15 | public static Continent AS = new Continent("AS") 16 | public static Continent OC = new Continent("OC") 17 | public static Continent AA = new Continent("AA") 18 | public static Continent OF = new Continent("OF") 19 | 20 | public static Map BY_NAME = [ 21 | EU: EU, 22 | UC: UC, 23 | MA: MA, 24 | SA: SA, 25 | AF: AF, 26 | AS: AS, 27 | OC: OC, 28 | AA: AA, 29 | OF: OF, 30 | ] 31 | 32 | public final String abbr 33 | private List countries = [] 34 | 35 | Continent(String abbr) { 36 | this.abbr = abbr 37 | } 38 | 39 | public List getCountries() { 40 | return countries 41 | } 42 | 43 | public void setCountries(String countries) { 44 | if (countries) { 45 | this.countries = countries.split(", ") as List 46 | } 47 | } 48 | 49 | public static void loadContinents() { 50 | BY_NAME.each { k, v -> 51 | v.setCountries(Config.getString("Continent.${k}.Countries")) 52 | } 53 | } 54 | 55 | public static boolean belongsToAnyContinent(String country) { 56 | return BY_NAME.any { k, v -> 57 | country in v.countries 58 | } 59 | } 60 | 61 | public static String getContinentFor(String country) { 62 | return I18N.getDefaultStringOrNull("Country.${country}.Continent") 63 | } 64 | 65 | public String toTxt() { 66 | return abbr 67 | } 68 | 69 | public String toString() { 70 | return I18N.getString("Continent.${abbr}") 71 | } 72 | } 73 | 74 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/FirstDefinition.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.TupleConstructor 5 | 6 | @CompileStatic 7 | @TupleConstructor 8 | class FirstDefinition { 9 | String line 10 | int lineNo 11 | Mod mod 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/Mod.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.ToString 5 | import groovy.transform.TupleConstructor 6 | import msfsmodmanager.state.* 7 | import msfsmodmanager.ui.I18N 8 | import msfsmodmanager.ex.ModsParseException 9 | 10 | @CompileStatic 11 | @ToString(includePackage=false, includeNames=true, excludes="description,author,url,active,file") 12 | class Mod { 13 | private static final String FLIGHTSIM_TO_URL_START = "https://flightsim.to/file/" 14 | public static final String FLIGHTSIM_TO_URL_WITH_WWW_START = "https://www.flightsim.to/file/" 15 | 16 | final String line 17 | final int lineNo 18 | 19 | final String name 20 | 21 | ModType type 22 | 23 | private String continentRaw 24 | Continent continent 25 | String country 26 | String city 27 | String icao 28 | private String aircraftTypeRaw 29 | AircraftType aircraftType 30 | 31 | String description 32 | String author 33 | String url 34 | 35 | boolean active 36 | 37 | public Mod(String name) { 38 | this.name = name 39 | this.line = null 40 | this.lineNo = -1 41 | } 42 | 43 | protected Mod(String name, String line, int lineNo) { 44 | this.name = name 45 | this.line = line 46 | this.lineNo = lineNo 47 | } 48 | 49 | public File getFile() { 50 | return new File((active ? FileSystem.MOD_DIR : FileSystem.TEMP_DIR) + $/\/$ + name) 51 | } 52 | 53 | public String toTxt() { 54 | return """${type.toTxt()}\t${continent ? continent.toTxt() : ''}\t$country\t$name\t${cityOrIcaoOrAircraftTypeToTxt()}${commentsToTxt()}""" 55 | } 56 | 57 | private String cityOrIcaoOrAircraftTypeToTxt() { 58 | String ret = "" 59 | if (city) ret = city 60 | else if (icao) ret = icao 61 | else if (aircraftType) ret = aircraftType.toTxt() 62 | 63 | if (ret) { 64 | ret += "\t" 65 | } 66 | return ret 67 | } 68 | 69 | private String commentsToTxt() { 70 | String ret = "" 71 | if (description) ret += description 72 | ret += "\t" 73 | if (author) ret += author 74 | ret += "\t" 75 | if (url) ret += url 76 | 77 | if (ret.trim()) { 78 | ret = "##\t" + ret 79 | } 80 | return ret 81 | } 82 | 83 | public void checkBasicCorrectness(CheckErrors checkErrors) { 84 | if (!name) { 85 | checkErrors.errorsByField.name.t = CheckErrorType.MISSING 86 | } 87 | if (!type) { 88 | checkErrors.errorsByField.type.t = CheckErrorType.MISSING 89 | } 90 | if (continentRaw && !continent) { 91 | checkErrors.errorsByField.continent.t = CheckErrorType.ILLEGAL 92 | } 93 | } 94 | 95 | public void checkCanonicalCorrectness(CheckErrors checkErrors, String fileName) { 96 | if (type == ModType.OTHER) { 97 | checkErrors.errorsByField.type.t = CheckErrorType.ILLEGAL 98 | } 99 | else if (type in [ModType.AIRPORT, ModType.LANDMARK, ModType.CITY, ModType.LANDSCAPE, ModType.LANDSCAPE_FIX]) { 100 | if (!continentRaw) { 101 | checkErrors.errorsByField.continent.t = CheckErrorType.MISSING 102 | } 103 | else if (!continent) { 104 | checkErrors.errorsByField.continent.t = CheckErrorType.ILLEGAL 105 | } 106 | else if (continent == Continent.OF) { 107 | checkErrors.errorsByField.continent.t = CheckErrorType.ILLEGAL 108 | } 109 | 110 | if (!country) { 111 | checkErrors.errorsByField.country.t = CheckErrorType.MISSING 112 | } 113 | else if (!isCanonicalCountry()) { 114 | checkErrors.errorsByField.country.t = CheckErrorType.ILLEGAL 115 | } 116 | 117 | if (!isCorrectContinent()) { 118 | checkErrors.errorsByField.continent.t = CheckErrorType.CANONICITY 119 | } 120 | 121 | if (type == ModType.AIRPORT) { 122 | if (!isCanonicalIcao()) { 123 | checkErrors.errorsByField.icao.t = CheckErrorType.ILLEGAL 124 | } 125 | } 126 | } 127 | else if (type in [ModType.LIVRERY, ModType.AIRCRAFT_MODEL]) { 128 | if (continentRaw) { 129 | if (!continent) { 130 | checkErrors.errorsByField.continent.t = CheckErrorType.MISSING 131 | } 132 | else if (continent == Continent.OF) { 133 | checkErrors.errorsByField.continent.t = CheckErrorType.ILLEGAL 134 | } 135 | } 136 | if (country) { 137 | if (!isCanonicalCountry()) { 138 | checkErrors.errorsByField.country.t = CheckErrorType.ILLEGAL 139 | } 140 | if (!isCorrectContinent()) { 141 | checkErrors.errorsByField.continent.t = CheckErrorType.CANONICITY 142 | } 143 | } 144 | 145 | if (!aircraftTypeRaw) { 146 | checkErrors.errorsByField.aircraftType.t = CheckErrorType.MISSING 147 | } 148 | else if (!aircraftType) { 149 | checkErrors.errorsByField.aircraftType.t = CheckErrorType.ILLEGAL 150 | } 151 | } 152 | 153 | if (!description) { 154 | checkErrors.errorsByField.description.t = CheckErrorType.MISSING 155 | } 156 | if (!author) { 157 | checkErrors.errorsByField.author.t = CheckErrorType.MISSING 158 | } 159 | 160 | if (!url) { 161 | checkErrors.errorsByField.url.t = CheckErrorType.MISSING 162 | } 163 | else if (!(url.startsWith(FLIGHTSIM_TO_URL_START) || url.startsWith(FLIGHTSIM_TO_URL_WITH_WWW_START))) { 164 | checkErrors.errorsByField.url.t = CheckErrorType.ILLEGAL 165 | } 166 | } 167 | 168 | private boolean isCanonicalCountry() { 169 | if (!country) return true 170 | 171 | return I18N.getDefaultStringOrNull("Country." + country) != null 172 | } 173 | 174 | private boolean isCorrectContinent() { 175 | if (!country) return true 176 | 177 | if (!continent) return true 178 | 179 | return Continent.getContinentFor(country) == continent.abbr 180 | } 181 | 182 | private boolean isCanonicalIcao() { 183 | return icao != null 184 | } 185 | 186 | public static Builder create(String name, String line, int lineNo) { 187 | return new Builder(name, line, lineNo) 188 | } 189 | 190 | public static class Builder { 191 | Mod mod 192 | 193 | public Builder(String name, String line, int lineNo) { 194 | this.mod = new Mod(name, line, lineNo) 195 | } 196 | 197 | public Builder type(String type) { 198 | this.mod.type = ModType.parse(type) 199 | return this 200 | } 201 | 202 | public Builder continent(String continent) { 203 | this.mod.continentRaw = continent 204 | this.mod.continent = this.mod.continentRaw ? Continent.BY_NAME[continent] : null 205 | return this 206 | } 207 | 208 | public Builder country(String country) { this.mod.country = country; return this } 209 | 210 | public Builder cityOrIcaoOrAircraft(String cityOrIcaoOrAircraft) { 211 | if (this.mod.type == ModType.AIRPORT) { 212 | if (isIcao(cityOrIcaoOrAircraft)) { 213 | this.mod.icao = cityOrIcaoOrAircraft 214 | } 215 | } 216 | else if (this.mod.type in [ModType.LIVRERY, ModType.AIRCRAFT_MODEL]) { 217 | this.mod.aircraftTypeRaw = cityOrIcaoOrAircraft 218 | this.mod.aircraftType = AircraftType.parse(cityOrIcaoOrAircraft) 219 | } 220 | else { 221 | this.mod.city = cityOrIcaoOrAircraft 222 | } 223 | return this 224 | } 225 | 226 | public Builder description(String description) { this.mod.description = description; return this } 227 | public Builder author(String author) { this.mod.author = author; return this } 228 | public Builder url(String url) { 229 | if (url != null) { 230 | url.replace("https://flightsim.to/", "https://www.flightsim.to/") 231 | } 232 | this.mod.url = url; 233 | return this 234 | } 235 | 236 | public Builder active(boolean active) { this.mod.active = active; return this } 237 | 238 | public Mod mod() { return mod } 239 | } 240 | 241 | private static boolean isIcao(String candidate) { 242 | // 1st OR: '?', an explicit "no ICAO given" marker 243 | // 2nd OR: 3-6 character ICAO 244 | // 3rd OR: local French ICAO codes 245 | return candidate ==~ /-|([A-Z0-9]{3,6})|(LF[0-9]{4})/ 246 | } 247 | 248 | // Hides Groovy's ugly inner class construction syntax (https://stackoverflow.com/a/27980914) 249 | public CheckErrors checkErrors(String fileName) { 250 | return new CheckErrors(fileName) 251 | } 252 | 253 | @TupleConstructor(includes="fileName") 254 | @ToString(includeNames=true) 255 | public class CheckErrors { 256 | String fileName 257 | 258 | Map errorsByField = [:].withDefault { new CheckErrorInfo() } 259 | 260 | public boolean containsAnyErrors() { 261 | return errorsByField.any { k, v -> v.t } 262 | } 263 | 264 | public void throwIfFailure() { 265 | if (containsAnyErrors()) { 266 | throw new ModsParseException.ModCheckException(fileName, this) 267 | } 268 | } 269 | 270 | public void throwSubException() { 271 | // checkBasicCorrectness 272 | if (errorsByField.name.t == CheckErrorType.MISSING) { 273 | throw new ModsParseException.MissingDataForLineParseException(fileName, "mod name", line, lineNo) 274 | } 275 | if (errorsByField.type.t == CheckErrorType.MISSING) { 276 | throw new ModsParseException.MissingDataForLineParseException(fileName, "mod type", line, lineNo) 277 | } 278 | if (errorsByField.continent.t == CheckErrorType.MISSING) { 279 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "continent", continentRaw, line, lineNo) 280 | } 281 | 282 | // checkCanonicalCorrectness 283 | if (errorsByField.type.t == CheckErrorType.ILLEGAL) { 284 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "mod type", type.abbr, line, lineNo) 285 | } 286 | if (errorsByField.continent.t == CheckErrorType.ILLEGAL) { 287 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "continent", continentRaw, line, lineNo) 288 | } 289 | if (errorsByField.country.t == CheckErrorType.MISSING) { 290 | throw new ModsParseException.MissingDataForLineParseException(fileName, "country", line, lineNo) 291 | } 292 | if (errorsByField.country.t == CheckErrorType.ILLEGAL) { 293 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "country", country, line, lineNo) 294 | } 295 | if (errorsByField.continent.t == CheckErrorType.CANONICITY) { 296 | throw new ModsParseException.IllegalContinentDataForLineParseException(fileName, continent.abbr, Continent.getContinentFor(country), country, line, lineNo) 297 | } 298 | if (errorsByField.icao.t == CheckErrorType.ILLEGAL) { 299 | throw new ModsParseException.MissingDataForLineParseException(fileName, "icao", line, lineNo) 300 | } 301 | if (errorsByField.aircraftType.t == CheckErrorType.MISSING) { 302 | throw new ModsParseException.MissingDataForLineParseException(fileName, "aircraft type", line, lineNo) 303 | } 304 | if (errorsByField.aircraftType.t == CheckErrorType.ILLEGAL) { 305 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "aircraft type", aircraftTypeRaw, line, lineNo) 306 | } 307 | if (errorsByField.description.t == CheckErrorType.MISSING) { 308 | throw new ModsParseException.MissingDataForLineParseException(fileName, "description", line, lineNo) 309 | } 310 | if (errorsByField.author.t == CheckErrorType.MISSING) { 311 | throw new ModsParseException.MissingDataForLineParseException(fileName, "author", line, lineNo) 312 | } 313 | if (errorsByField.url.t == CheckErrorType.MISSING) { 314 | throw new ModsParseException.MissingDataForLineParseException(fileName, "url", line, lineNo) 315 | } 316 | if (errorsByField.url.t == CheckErrorType.ILLEGAL) { 317 | throw new ModsParseException.IllegalDataForLineParseException(fileName, "url", url, line, lineNo) 318 | } 319 | 320 | // Mods.checkRepositoryConsistency 321 | if (errorsByField.name.t == CheckErrorType.DUPLICATE) { 322 | throw new ModsParseException.DuplicateModNameForLineParseException(Mods.instance.fileName, errorsByField.name.payload, line, lineNo) 323 | } 324 | if (errorsByField.continent.t == CheckErrorType.COUNTRY_TO_CONTINENT_CONSISTENCY) { 325 | throw new ModsParseException.ConflictingDataForLineParseException(Mods.instance.fileName, "continent", errorsByField.continent.payload, line, lineNo) 326 | } 327 | if (errorsByField.country.t == CheckErrorType.CITY_TO_COUNTRY_CONSISTENCY) { 328 | throw new ModsParseException.ConflictingDataForLineParseException(Mods.instance.fileName, "country", errorsByField.country.payload, line, lineNo) 329 | } 330 | if (errorsByField.continent.t == CheckErrorType.CITY_TO_CONTINENT_CONSISTENCY) { 331 | throw new ModsParseException.ConflictingDataForLineParseException(Mods.instance.fileName, "continent", errorsByField.continent.payload, line, lineNo) 332 | } 333 | } 334 | 335 | public Mod getMod() { 336 | return Mod.this 337 | } 338 | } 339 | 340 | @ToString(includeNames=true) 341 | public static class CheckErrorInfo { 342 | CheckErrorType t 343 | FirstDefinition payload 344 | } 345 | 346 | // can't make this an inner class of the inner class CheckErrors because the latter would have to be static 347 | public static enum CheckErrorType { 348 | MISSING, 349 | ILLEGAL, 350 | DUPLICATE, 351 | CANONICITY, 352 | COUNTRY_TO_CONTINENT_CONSISTENCY, 353 | CITY_TO_COUNTRY_CONSISTENCY, 354 | CITY_TO_CONTINENT_CONSISTENCY, 355 | } 356 | } 357 | 358 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/ModType.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.TupleConstructor 5 | 6 | import msfsmodmanager.state.Config 7 | import msfsmodmanager.ui.I18N 8 | 9 | @CompileStatic 10 | @TupleConstructor 11 | enum ModType { 12 | AIRPORT("AP"), 13 | LANDMARK("LM"), 14 | CITY("CT"), 15 | LANDSCAPE("LS"), 16 | LANDSCAPE_FIX("LF"), 17 | LIVRERY("AL"), 18 | AIRCRAFT_MODEL("AM"), 19 | OTHER("OT"), 20 | 21 | public static List defaultSelection 22 | 23 | static { 24 | String config = Config.getString("ModType.defaultSelection") 25 | defaultSelection = (config.split(", ") as List).collect { 26 | parse(it) 27 | } 28 | } 29 | 30 | String abbr 31 | 32 | public String toTxt() { 33 | return abbr 34 | } 35 | 36 | public static ModType parse(String abbr) { 37 | return values().find { it.abbr == abbr } 38 | } 39 | 40 | public String toString() { 41 | return I18N.getString("ModType.${abbr}.dropdown") 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/model/Selection.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.model 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.ToString 5 | 6 | @CompileStatic 7 | @ToString 8 | class Selection { 9 | List types = [] 10 | 11 | List continents 12 | List countries = [] 13 | List cities = [] 14 | 15 | List aircraftTypes = [] 16 | 17 | public Selection(List continentNames) { 18 | continents = continentNames.collect { Continent.BY_NAME[it] } 19 | } 20 | 21 | public boolean activates(Mod mod) { 22 | if (!(mod.type in types)) { 23 | return false 24 | } 25 | 26 | if (mod.type in [ModType.LIVRERY, ModType.AIRCRAFT_MODEL]) { 27 | if (!mod.aircraftType || mod.aircraftType in aircraftTypes) { 28 | return true 29 | } 30 | else { 31 | return false 32 | } 33 | } 34 | 35 | if (mod.city in cities) { 36 | return true 37 | } 38 | else if (mod.city == null || mod.city in Cities.DISABLED_CITIES) { 39 | if (mod.country in countries) { 40 | return true 41 | } 42 | else if (mod.continent == null) { 43 | return true 44 | } 45 | else if (!(mod.country in mod.continent.countries)) { 46 | if (mod.continent in continents) { 47 | return true 48 | } 49 | } 50 | } 51 | 52 | return false 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/state/Config.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.state 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class Config { 7 | private static final File BASE_DIR = FileSystem.getJarPath() 8 | private static final String BASE_NAME = "config" 9 | private static ResourceBundle bundle 10 | 11 | static { 12 | init() 13 | } 14 | 15 | private static init() { 16 | URL[] urls = [BASE_DIR.toURI().toURL()]; 17 | ClassLoader loader = new URLClassLoader(urls); 18 | bundle = ResourceBundle.getBundle(BASE_NAME, Locale.getDefault(), loader); 19 | } 20 | 21 | public static String getStringOrNull(String key) { 22 | try { 23 | // see https://stackoverflow.com/a/6995374 (Java 8 issue) 24 | return new String(bundle.getString(key).getBytes("ISO-8859-1"), "UTF-8") 25 | } 26 | catch (MissingResourceException ex) { 27 | return null 28 | } 29 | } 30 | 31 | public static String getString(String key) { 32 | String ret = getStringOrNull(key) 33 | return ret != null ? ret : "??? $key ???" 34 | } 35 | 36 | public static void setString(String key, String value) { 37 | value = value.replaceAll("\\\\", "\\\\\\\\") 38 | File bundleFile = new File(BASE_DIR.absolutePath + "\\" + BASE_NAME + ".properties") 39 | String text = bundleFile.getText("UTF-8") 40 | text = text.replace("$key=\n", "$key=$value\n") 41 | bundleFile.setText(text, "UTF-8") 42 | init() 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/state/FileSystem.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.state 2 | 3 | import groovy.transform.CompileStatic 4 | import java.nio.file.Files 5 | import static java.nio.file.StandardCopyOption.*; 6 | import msfsmodmanager.Main 7 | import msfsmodmanager.model.* 8 | import msfsmodmanager.state.Config 9 | 10 | @CompileStatic 11 | class FileSystem { 12 | public static String MOD_DIR 13 | public static String TEMP_DIR 14 | 15 | public static void init() { 16 | MOD_DIR = Config.getString("path.community") 17 | TEMP_DIR = Config.getString("path.temp") 18 | 19 | if (MOD_DIR == "") { 20 | Main.FIRST_START = true 21 | Config.setString("path.community", getJarPath().absolutePath + /\Community/) 22 | MOD_DIR = Config.getString("path.community") 23 | } 24 | 25 | if (TEMP_DIR == "") { 26 | Config.setString("path.temp", getJarPath().absolutePath + /\Temp/) 27 | TEMP_DIR = Config.getString("path.temp") 28 | } 29 | 30 | File tempDir = new File(TEMP_DIR) 31 | if (!tempDir.exists()) { 32 | tempDir.mkdir() 33 | } 34 | 35 | Mods.instance.modInfoFile = new File(getJarPath().absolutePath + /\mods.txt/) 36 | ModsDb.instance.modInfoFile = new File(getJarPath().absolutePath + /\mod-repository\mods-db.txt/) 37 | 38 | ModsDb.UPDATE_WITH_GIT = Config.getString("ModsDb.updateWithGit") == "true" 39 | } 40 | 41 | public static File getJarPath() { 42 | File file = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); 43 | if (file.name.endsWith(".jar")) { 44 | file = file.parentFile 45 | } 46 | return file 47 | } 48 | 49 | public static boolean isActive(String modName) { 50 | return new File(MOD_DIR + $/\/$ + modName).exists() 51 | } 52 | 53 | public static void activate(Mod mod) { 54 | if (mod.active) { 55 | return 56 | } 57 | Files.move(mod.file.toPath(), new File(MOD_DIR + $/\/$ + mod.name).toPath(), REPLACE_EXISTING); 58 | } 59 | 60 | public static void deactivate(Mod mod) { 61 | if (!mod.active) { 62 | return 63 | } 64 | Files.move(mod.file.toPath(), new File(TEMP_DIR + $/\/$ + mod.name).toPath(), REPLACE_EXISTING); 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/state/MasterData.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.state 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.model.Continent 5 | import msfsmodmanager.ui.I18N 6 | 7 | @CompileStatic 8 | class MasterData { 9 | public static Set allAuthors 10 | public static Map allCountriesByAbbr 11 | public static Map allCountriesByName 12 | public static Map> allCitiesByCountry = [:].withDefault { new TreeSet(String.CASE_INSENSITIVE_ORDER) } 13 | public static Map> allCitiesByContinent = [:].withDefault { new TreeSet(String.CASE_INSENSITIVE_ORDER) } 14 | public static Set allCitiesWithoutContinent 15 | 16 | public static void init() { 17 | Set temp = (Mods.instance.mods.collect { it.author } + 18 | ModsDb.instance.mods.collect { it.author } ).findAll { it } as Set 19 | allAuthors = new TreeSet(String.CASE_INSENSITIVE_ORDER) 20 | allAuthors.addAll(temp) 21 | 22 | allCountriesByAbbr = I18N.getAllKeysMatching(/Country\.[A-Za-z]{2,3}/).collectEntries { String key -> 23 | String abbr = key - "Country." 24 | String name = I18N.getString(key) 25 | if (Continent.getContinentFor(abbr) == "UC") { 26 | if (key != "Country.CAN") { 27 | name += " (US)" 28 | } 29 | } 30 | return [(abbr): name] 31 | } 32 | 33 | allCountriesByName = I18N.getAllKeysMatching(/Country\.[A-Za-z]{2,3}/).collectEntries { String key -> 34 | String abbr = key - "Country." 35 | String name = I18N.getString(key) 36 | if (Continent.getContinentFor(abbr) == "UC") { 37 | if (key != "Country.CAN") { 38 | name += " (US)" 39 | } 40 | } 41 | return [(name): abbr] 42 | } 43 | 44 | Map countriesByCity = 45 | (Mods.instance.mods.collectEntries { [(it.city): it.country] } + 46 | ModsDb.instance.mods.collectEntries { [(it.city): it.country] } ) 47 | countriesByCity.each { city, country -> 48 | if (city) allCitiesByCountry[country] << city 49 | } 50 | 51 | Map continentsByCity = 52 | (Mods.instance.mods.collectEntries { [(it.city): it.country ? null : it.continent] } + 53 | ModsDb.instance.mods.collectEntries { [(it.city): it.country ? null : it.continent] } ) 54 | continentsByCity.each { city, continent -> 55 | if (city) allCitiesByContinent[continent] << city 56 | } 57 | 58 | temp = ((Mods.instance.mods.collect { it.city } + 59 | ModsDb.instance.mods.collect { it.city } ).findAll { it } as Set) - 60 | countriesByCity.findAll { it.value }.keySet() - continentsByCity.findAll { it.value }.keySet() 61 | 62 | allCitiesWithoutContinent = new TreeSet(String.CASE_INSENSITIVE_ORDER) 63 | allCitiesWithoutContinent.addAll(temp) 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/state/Mods.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.state 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.io.FileType 5 | import msfsmodmanager.ex.ModsParseException 6 | import msfsmodmanager.model.* 7 | import msfsmodmanager.util.StringUtil 8 | 9 | @CompileStatic 10 | class Mods { 11 | public static final Mods instance = new Mods() 12 | 13 | File modInfoFile 14 | protected Map modsByName = [:] 15 | FirstDefinitions firstDefinitions 16 | 17 | public String getFileName() { 18 | return "mods.txt" 19 | } 20 | 21 | public void loadRegisteredMods() { 22 | // reset here to not keep anything in memory 23 | modsByName.clear() 24 | resetFirstDefinitions() 25 | 26 | String text = modInfoFile.getText("UTF-8") 27 | /* 28 | * For very unknown reasons, a file might have a bogus character at the very beginning. 29 | * Also for very unknown reasons, we cannot reliably match its presense / non-presense with regex. 30 | * Thus, match against all known ModTypes since a ModType's first character should be the first character in mods.txt. 31 | */ 32 | while (!text.empty && !ModType.any { ModType it -> text.startsWith(it.name()[0]) }) { 33 | text = text.substring(1) 34 | } 35 | 36 | addAllMods(text.split("\n") as List) 37 | } 38 | 39 | public void addAllMods(List lines) { 40 | modsByName.putAll(lines.findAll { 41 | it.trim() 42 | }.withIndex().collectEntries { String it, int i -> 43 | i = i+1 44 | 45 | Mod mod = null 46 | try { 47 | mod = parseLine(it, i) 48 | } 49 | catch (Exception ex) { 50 | throw new ModsParseException.LineParseException(fileName, it, i, ex) 51 | } 52 | 53 | Mod.CheckErrors checkErrors = mod.checkErrors(fileName) 54 | mod.checkBasicCorrectness(checkErrors) 55 | checkRepositoryConsistency(checkErrors, mod, it, i) 56 | 57 | 58 | return [(mod.name): mod] 59 | }) 60 | } 61 | 62 | public static Mod parseSafeLine(String line) { 63 | Mod mod 64 | if (!line.contains("\t")) { 65 | mod = new Mod(line) 66 | } 67 | else { 68 | mod = parseLine(line, -1) 69 | } 70 | } 71 | 72 | private static Mod parseLine(String it, int i) { 73 | it = it.trim() 74 | String[] line = it.split("##", -1) 75 | 76 | List basic = StringUtil.trimEnd(line[0]).split("\t", -1) as List 77 | 78 | String type = basic[0] 79 | Mod.Builder builder = Mod.create(basic[3], it, i) 80 | .type(basic[0]) 81 | .continent(basic[1]) 82 | .country(basic[2]) 83 | .cityOrIcaoOrAircraft(basic.size() > 4 ? basic[4] : null) 84 | .active(FileSystem.isActive(basic[3])) 85 | 86 | if (line.size() > 1) { 87 | List more = StringUtil.trimStart(line[1]).split("\t", -1) as List 88 | builder = builder 89 | .description(more[0]) 90 | .author(more[1]) 91 | .url(more[2]) 92 | } 93 | 94 | Mod mod = builder.mod() 95 | 96 | return mod 97 | } 98 | 99 | public void addAllMods(Map newMods, boolean collectErrors=false) { 100 | // reset here to not keep anything in memory 101 | resetFirstDefinitions() 102 | 103 | mods.each { Mod mod -> 104 | Mod.CheckErrors checkErrors = mod.checkErrors(fileName) 105 | checkRepositoryConsistency(checkErrors, mod, mod.line, mod.lineNo) 106 | } 107 | 108 | Map checkErrorsByMod = [:] 109 | newMods.each { String name, Mod mod -> 110 | try { 111 | Mod.CheckErrors checkErrors = mod.checkErrors(fileName) 112 | checkCorrectness(checkErrors, mod) 113 | checkRepositoryConsistency(checkErrors, mod, mod.line, mod.lineNo) 114 | checkErrors.throwIfFailure() 115 | } 116 | catch(ModsParseException.ModCheckException ex) { 117 | if (collectErrors) { 118 | checkErrorsByMod[ex.checkErrors.mod.name] = ex.checkErrors 119 | } 120 | else { 121 | ex.checkErrors.throwSubException() 122 | } 123 | } 124 | } 125 | 126 | if (!checkErrorsByMod.empty) { 127 | throw new ModsParseException.MultipleModsCheckException(checkErrorsByMod) 128 | } 129 | 130 | modsByName.putAll(newMods) 131 | } 132 | 133 | protected void checkCorrectness(Mod.CheckErrors checkErrors, Mod mod) { 134 | mod.checkBasicCorrectness(checkErrors) 135 | } 136 | 137 | protected void checkRepositoryConsistency(Mod.CheckErrors checkErrors, Mod mod, String line, int lineNo) { 138 | firstDefinitions.checkDuplicateModNames(checkErrors, line, lineNo, mod) 139 | firstDefinitions.checkCityCountryDefinitionConsistency(checkErrors, line, lineNo, mod) 140 | firstDefinitions.checkCountryDefinitionConsistency(checkErrors, line, lineNo, mod) 141 | firstDefinitions.checkCityContinentDefinitionConsistency(checkErrors, line, lineNo, mod) 142 | } 143 | 144 | public List getModNames() { 145 | return modsByName.keySet() as List 146 | } 147 | 148 | public List getMods() { 149 | return modsByName.values() as List 150 | } 151 | 152 | public List findAllMods() { 153 | List ret = [] 154 | 155 | new File(FileSystem.MOD_DIR).eachFile(FileType.DIRECTORIES) { 156 | ret << it 157 | } 158 | new File(FileSystem.TEMP_DIR).eachFile(FileType.DIRECTORIES) { 159 | ret << it 160 | } 161 | return ret 162 | } 163 | 164 | public Map getUserDefinedMods() { 165 | List keys = (getModNames() - ModsDb.instance.getModNames()) 166 | 167 | return modsByName.subMap(keys) 168 | } 169 | 170 | public void saveToTxt() { 171 | String text = sortBeforeSaving(mods)*.toTxt().join("\n") 172 | 173 | modInfoFile.setText(text, "UTF-8") 174 | } 175 | 176 | protected List sortBeforeSaving(List mods) { 177 | return mods 178 | } 179 | 180 | /* 181 | * For very unknown reasons, this gives a compile error if called like that in a child class. 182 | * It works, however, when encapsulated in a method. 183 | */ 184 | protected void resetFirstDefinitions() { 185 | firstDefinitions = new FirstDefinitions() 186 | } 187 | 188 | protected class FirstDefinitions { 189 | Map modNames = [:] 190 | Map continentByCountry = [:] 191 | Map countryByCity = [:] 192 | Map continentByCity = [:] 193 | 194 | protected void checkDuplicateModNames(Mod.CheckErrors checkErrors, String line, int lineNo, Mod mod) { 195 | if (checkErrors.errorsByField.name.t) return 196 | 197 | if (!modNames[mod.name]) { 198 | modNames[mod.name] = new FirstDefinition(line, lineNo, mod) 199 | } 200 | else { 201 | FirstDefinition firstDefinition = modNames[mod.name] 202 | checkErrors.errorsByField.name.t = Mod.CheckErrorType.DUPLICATE 203 | } 204 | } 205 | 206 | protected void checkCountryDefinitionConsistency(Mod.CheckErrors checkErrors, String line, int lineNo, Mod mod) { 207 | if (checkErrors.errorsByField.country.t) return 208 | 209 | if (!mod.country) { 210 | return 211 | } 212 | 213 | if (!continentByCountry[mod.country]) { 214 | continentByCountry[mod.country] = new FirstDefinition(line, lineNo, mod) 215 | } 216 | else { 217 | FirstDefinition firstDefinition = continentByCountry[mod.country] 218 | if (firstDefinition.mod.continent != mod.continent) { 219 | checkErrors.errorsByField.continent.t = Mod.CheckErrorType.COUNTRY_TO_CONTINENT_CONSISTENCY 220 | checkErrors.errorsByField.continent.payload = firstDefinition 221 | } 222 | } 223 | } 224 | 225 | protected void checkCityCountryDefinitionConsistency(Mod.CheckErrors checkErrors, String line, int lineNo, Mod mod) { 226 | if (checkErrors.errorsByField.city.t) return 227 | 228 | if (!mod.city) { 229 | return 230 | } 231 | 232 | if (!countryByCity[mod.city]) { 233 | countryByCity[mod.city] = new FirstDefinition(line, lineNo, mod) 234 | } 235 | else { 236 | FirstDefinition firstDefinition = countryByCity[mod.city] 237 | if (firstDefinition.mod.country != mod.country) { 238 | checkErrors.errorsByField.country.t = Mod.CheckErrorType.CITY_TO_COUNTRY_CONSISTENCY 239 | checkErrors.errorsByField.country.payload = firstDefinition 240 | } 241 | } 242 | } 243 | 244 | protected void checkCityContinentDefinitionConsistency(Mod.CheckErrors checkErrors, String line, int lineNo, Mod mod) { 245 | if (checkErrors.errorsByField.city.t) return 246 | 247 | if (!mod.city) { 248 | return 249 | } 250 | 251 | if (!continentByCity[mod.city]) { 252 | continentByCity[mod.city] = new FirstDefinition(line, lineNo, mod) 253 | } 254 | else { 255 | FirstDefinition firstDefinition = continentByCity[mod.city] 256 | if (firstDefinition.mod.continent != mod.continent) { 257 | checkErrors.errorsByField.continent.t = Mod.CheckErrorType.CITY_TO_CONTINENT_CONSISTENCY 258 | checkErrors.errorsByField.continent.payload = firstDefinition 259 | } 260 | } 261 | } 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/state/ModsDb.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.state 2 | 3 | import groovy.time.TimeCategory 4 | import groovy.time.TimeDuration 5 | import groovy.transform.CompileStatic 6 | import msfsmodmanager.Main 7 | import msfsmodmanager.logic.ModsDbHandler 8 | import msfsmodmanager.model.* 9 | import msfsmodmanager.util.WebReader 10 | 11 | @CompileStatic 12 | class ModsDb extends Mods { 13 | public static final String HTTP_UPDATE_URL = "https://raw.githubusercontent.com/captn-nick/MSFS-Mod-Repository/master/mods-db.txt" 14 | public static boolean UPDATE_WITH_GIT 15 | 16 | public static final ModsDb instance = new ModsDb() 17 | 18 | public String getFileName() { 19 | return "mod-repository\\mods-db.txt" 20 | } 21 | 22 | public DbInformationFound replaceWithDbInformation(List modNames) { 23 | DbInformationFound ret = new DbInformationFound() 24 | 25 | ret.lines = modNames.collect { String modName -> 26 | Mod mod = modsByName[modName] 27 | 28 | if (mod) { 29 | ret.foundAny = true 30 | return mod.toTxt() 31 | } 32 | else { 33 | return modName 34 | } 35 | } 36 | 37 | return ret 38 | } 39 | 40 | public static class DbInformationFound { 41 | boolean foundAny 42 | List lines 43 | } 44 | 45 | public boolean isUpdatable() { 46 | if (Main.FIRST_START) { 47 | return true 48 | } 49 | 50 | Date fileModified = new Date(modInfoFile.lastModified()) 51 | TimeDuration sinceModified = TimeCategory.minus(new Date(), fileModified) 52 | 53 | return sinceModified >= new TimeDuration(0, 10, 0, 0) 54 | } 55 | 56 | public boolean update(Closure okButtonAction) { 57 | boolean success = true 58 | if (UPDATE_WITH_GIT) { 59 | success = ModsDbHandler.updateWithGit(okButtonAction) 60 | } 61 | else { 62 | ModsDbHandler.updateWithHttp() 63 | } 64 | 65 | return success 66 | } 67 | 68 | protected void checkCorrectness(Mod.CheckErrors checkErrors, Mod mod) { 69 | mod.checkBasicCorrectness(checkErrors) 70 | mod.checkCanonicalCorrectness(checkErrors, Mods.instance.fileName) 71 | } 72 | 73 | protected List sortBeforeSaving(List mods) { 74 | return mods.sort { it.name.toLowerCase() } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/Dialogs.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui 2 | 3 | import groovy.transform.CompileStatic 4 | import javax.swing.JOptionPane 5 | import msfsmodmanager.state.ModsDb 6 | 7 | @CompileStatic 8 | class Dialogs { 9 | public static boolean updateModsDb() { 10 | int result = JOptionPane.showConfirmDialog(null, """Would you like to update your mods database via the internet now? 11 | 12 | The mods database is used to look up information of mods you haven't registered yet to automate the registration process.""", 13 | "Welcome to MSFS Mod Selector", JOptionPane.YES_NO_OPTION); 14 | return result == JOptionPane.YES_OPTION 15 | } 16 | 17 | public static void updateModsDbSuccessful() { 18 | JOptionPane.showMessageDialog(null, """Mods database successfully updated.""", 19 | "Update successful", JOptionPane.INFORMATION_MESSAGE); 20 | } 21 | 22 | public static void afterContributedToModsDb() { 23 | JOptionPane.showInputDialog(null, """Your user-defined mods have been added to "${ModsDb.instance.fileName}". 24 | 25 | Please use your preferred Git client to push it to the following central repository file:""", "Thank you.", JOptionPane.INFORMATION_MESSAGE, null, null, 26 | "https://github.com/captn-nick/MSFS-Mod-Repository/blob/master/mods-db.txt") 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/I18N.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.Main 5 | import msfsmodmanager.state.FileSystem 6 | 7 | @CompileStatic 8 | class I18N { 9 | private static final File BASE_DIR = FileSystem.getJarPath() 10 | private static final String BASE_NAME = "labels" 11 | private static ResourceBundle bundle 12 | 13 | private static ResourceBundle defaultBundle = ResourceBundle.getBundle("default_labels", Locale.getDefault()); 14 | 15 | static { 16 | try { 17 | init() 18 | } 19 | catch (MissingResourceException ex) { 20 | // no user-defined resource bundle set 21 | } 22 | } 23 | 24 | private static init() { 25 | URL[] urls = [BASE_DIR.toURI().toURL()]; 26 | ClassLoader loader = new URLClassLoader(urls); 27 | bundle = ResourceBundle.getBundle(BASE_NAME, Locale.getDefault(), loader); 28 | } 29 | 30 | public static String getCountryWithContinent(String key) { 31 | String ret = getString(key) 32 | if (getString(key + ".Continent") == "UC") { 33 | ret += " (US)" 34 | } 35 | return ret 36 | } 37 | 38 | public static String getString(String key) { 39 | String ret = null 40 | 41 | if (bundle != null) { 42 | ret = getStringOrNull(key, bundle) 43 | } 44 | 45 | if (ret == null) { 46 | ret = getDefaultStringOrNull(key) 47 | } 48 | 49 | return ret != null ? ret : "??? $key ???" 50 | } 51 | 52 | public static String getDefaultStringOrNull(String key) { 53 | return getStringOrNull(key, defaultBundle) 54 | } 55 | 56 | private static String getStringOrNull(String key, ResourceBundle rb) { 57 | try { 58 | // see https://stackoverflow.com/a/6995374 (Java 8 issue) 59 | return new String(rb.getString(key).getBytes("ISO-8859-1"), "UTF-8") 60 | } 61 | catch (MissingResourceException ex) { 62 | return null 63 | } 64 | } 65 | 66 | public static Set getAllKeysMatching(String regex) { 67 | Set all = [] as Set 68 | if (bundle != null) { 69 | all = bundle.getKeys().toSet() 70 | } 71 | all += defaultBundle.getKeys().toSet() 72 | 73 | return all.findAll { 74 | it ==~ regex 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/ColoredListCellRenderer.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit; 2 | 3 | // based on http://www.java2s.com/Tutorial/Java/0240__Swing/AddyourownListCellRenderer.htm 4 | import java.awt.Component; 5 | import javax.swing.DefaultListCellRenderer; 6 | import javax.swing.JLabel; 7 | import javax.swing.JList; 8 | import javax.swing.ListCellRenderer; 9 | 10 | public class ColoredListCellRenderer implements ListCellRenderer { 11 | private static DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); 12 | 13 | private EditModsFrameHandler handler; 14 | 15 | public ColoredListCellRenderer(EditModsFrameHandler handler) { 16 | this.handler = handler; 17 | } 18 | 19 | @Override 20 | public Component getListCellRendererComponent(JList list, Object value, int index, 21 | boolean isSelected, boolean cellHasFocus) { 22 | JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index, 23 | isSelected, cellHasFocus); 24 | if (handler.checkErrorsByMod.containsKey(value) && handler.checkErrorsByMod.get(value).containsAnyErrors()) { 25 | renderer.setBackground(EditModsFrame.CHECK_ERROR_COLOR); 26 | if (isSelected) { 27 | renderer.setBackground(renderer.getBackground().darker()); 28 | } 29 | } 30 | return renderer; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/EditModsFrameHandler.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit 2 | 3 | import javax.swing.JComponent 4 | import groovy.transform.CompileStatic 5 | import msfsmodmanager.Main 6 | import msfsmodmanager.ex.ModsParseException 7 | import msfsmodmanager.model.Mod 8 | import msfsmodmanager.model.ModType 9 | import msfsmodmanager.model.Continent 10 | import msfsmodmanager.model.AircraftType 11 | import msfsmodmanager.state.* 12 | import msfsmodmanager.ui.* 13 | import msfsmodmanager.util.SwingUtil 14 | 15 | @CompileStatic 16 | class EditModsFrameHandler { 17 | EditModsFrame frame 18 | 19 | Mod mod 20 | 21 | public final String[] modNames 22 | private final Map mods 23 | public final Set allAuthors 24 | public final Set allModTypes 25 | public final Set allContinents 26 | public final Set allCountries 27 | public final Map> allCitiesByCountry 28 | public final Map> allCitiesByContinent 29 | public final Set allCitiesWithoutContinent 30 | public final Set allAircraftTypes 31 | 32 | public boolean changesModSelection 33 | 34 | public Map checkErrorsByMod = [:] 35 | 36 | private Set messagePanels 37 | 38 | public EditModsFrameHandler(EditModsFrame frame, List mods) { 39 | this.frame = frame 40 | 41 | this.mods = mods.collectEntries { 42 | [(it.name): it] 43 | } 44 | modNames = mods*.name as String[] 45 | allAuthors = MasterData.allAuthors 46 | allModTypes = ModType.values() as TreeSet // TreeSet keeps insert order for enum values 47 | allContinents = Continent.BY_NAME.values() as Set 48 | 49 | allCountries = MasterData.allCountriesByName.entrySet().sort { it -> 50 | // sort by translated label name, but all U.S. states (except Canada) at the end of the list 51 | // it = the entry. Note that the map is flipped: key = the label, value = "Country.$code" 52 | if (Continent.getContinentFor(it.value) == "UC" && it.value != "CAN") { 53 | return it.key 54 | } 55 | else { 56 | return "\t" + it.key 57 | } 58 | }*.key as LinkedHashSet // LinkedHashSet to preserve insertion / sort order 59 | 60 | allCitiesByCountry = MasterData.allCitiesByCountry 61 | allCitiesByContinent = MasterData.allCitiesByContinent 62 | allCitiesWithoutContinent = MasterData.allCitiesWithoutContinent 63 | 64 | allAircraftTypes = AircraftType.values() as TreeSet // TreeSet keeps insert order for enum values 65 | } 66 | 67 | public void initFrame() { 68 | messagePanels = [ 69 | frame.jPanel1Msgs, 70 | frame.jPanel2Msgs, 71 | frame.jPanel3Msgs, 72 | frame.jPanel4Msgs, 73 | frame.jPanel5Msgs, 74 | ] as Set 75 | 76 | messagePanels.each { 77 | it.parentComponent = (JComponent)(it.parent) 78 | it.previousComponent = SwingUtil.getPreviousComponent(it) 79 | } 80 | 81 | allAuthors.each { frame.authorComboBox.addItem(it) } 82 | allModTypes.each { frame.typeComboBox.addItem(it) } 83 | allContinents.each { frame.continentComboBox.addItem(it) } 84 | allCountries.each { frame.countryComboBox.addItem(it) } 85 | allAircraftTypes.each { frame.aircraftTypeComboBox.addItem(it) } 86 | 87 | setMod(modNames[0]) 88 | 89 | updateMessagePanels() 90 | } 91 | 92 | public void setMod(String modName) { 93 | mod = mods[modName] 94 | load() 95 | } 96 | 97 | private void load() { 98 | changesModSelection = true 99 | 100 | frame.urlTextField.text = mod.url 101 | frame.descriptionTextField.text = mod.description 102 | frame.authorComboBox.text = mod.author 103 | 104 | frame.typeComboBox.text = mod.type 105 | frame.continentComboBox.text = mod.continent 106 | frame.countryComboBox.text = MasterData.allCountriesByAbbr[mod.country] 107 | 108 | frame.cityComboBox.text = mod.city 109 | frame.icaoTextField.text = mod.icao 110 | frame.aircraftTypeComboBox.text = mod.aircraftType 111 | 112 | changeType() 113 | 114 | changesModSelection = false 115 | } 116 | 117 | public void save() { 118 | mod.url = frame.urlTextField.text 119 | mod.description = frame.descriptionTextField.text 120 | mod.author = frame.authorComboBox.text 121 | 122 | mod.type = frame.typeComboBox.text 123 | mod.continent = frame.continentComboBox.text 124 | mod.country = MasterData.allCountriesByName[frame.countryComboBox.text] 125 | 126 | mod.city = frame.cityComboBox.text 127 | mod.icao = frame.icaoTextField.text 128 | mod.aircraftType = frame.aircraftTypeComboBox.text 129 | } 130 | 131 | public void changeType() { 132 | switch (frame.typeComboBox.text) { 133 | case [ModType.AIRPORT]: 134 | frame.jPanel5.remove(frame.cityPanel) 135 | frame.jPanel5.add(frame.icaoPanel) 136 | frame.jPanel5.remove(frame.aircraftTypePanel) 137 | 138 | frame.jPanel5Msgs.remove(frame.cityMsgLabel) 139 | frame.jPanel5Msgs.add(frame.icaoMsgLabel) 140 | frame.jPanel5Msgs.remove(frame.aircraftTypeMsgLabel) 141 | break 142 | case [ModType.LANDMARK, ModType.CITY, ModType.LANDSCAPE, ModType.LANDSCAPE_FIX]: 143 | frame.jPanel5.add(frame.cityPanel) 144 | frame.jPanel5.remove(frame.icaoPanel) 145 | frame.jPanel5.remove(frame.aircraftTypePanel) 146 | 147 | frame.jPanel5Msgs.add(frame.cityMsgLabel) 148 | frame.jPanel5Msgs.remove(frame.icaoMsgLabel) 149 | frame.jPanel5Msgs.remove(frame.aircraftTypeMsgLabel) 150 | break 151 | case [ModType.LIVRERY, ModType.AIRCRAFT_MODEL]: 152 | frame.jPanel5.remove(frame.cityPanel) 153 | frame.jPanel5.remove(frame.icaoPanel) 154 | frame.jPanel5.add(frame.aircraftTypePanel) 155 | 156 | frame.jPanel5Msgs.remove(frame.cityMsgLabel) 157 | frame.jPanel5Msgs.remove(frame.icaoMsgLabel) 158 | frame.jPanel5Msgs.add(frame.aircraftTypeMsgLabel) 159 | break 160 | default: 161 | frame.jPanel5.remove(frame.cityPanel) 162 | frame.jPanel5.remove(frame.icaoPanel) 163 | frame.jPanel5.remove(frame.aircraftTypePanel) 164 | 165 | frame.jPanel5Msgs.remove(frame.cityMsgLabel) 166 | frame.jPanel5Msgs.remove(frame.icaoMsgLabel) 167 | frame.jPanel5Msgs.remove(frame.aircraftTypeMsgLabel) 168 | } 169 | frame.revalidate() 170 | frame.repaint() 171 | } 172 | 173 | public void changeCountry() { 174 | if (frame.countryComboBox.text == null) { 175 | changeContinent() 176 | return 177 | } 178 | 179 | String abbr = MasterData.allCountriesByName[frame.countryComboBox.text] 180 | 181 | // Don't change continent as a result of switching to another mod for editing 182 | if (!changesModSelection) { 183 | frame.continentComboBox.text = Continent.BY_NAME[Continent.getContinentFor(abbr)] 184 | } 185 | 186 | frame.cityComboBox.removeAllItems() 187 | allCitiesByCountry[abbr].each { frame.cityComboBox.addItem(it) } 188 | } 189 | 190 | public void changeContinent() { 191 | if (frame.countryComboBox.text == null) { 192 | frame.cityComboBox.removeAllItems() 193 | if (frame.continentComboBox.text == null) { 194 | allCitiesWithoutContinent.each { frame.cityComboBox.addItem(it) } 195 | return 196 | } 197 | 198 | allCitiesByContinent[frame.continentComboBox.text].each { frame.cityComboBox.addItem(it) } 199 | } 200 | } 201 | 202 | public void addAll() { 203 | mods.values().each { Mod mod -> 204 | switch (mod.type) { 205 | case [ModType.AIRPORT]: 206 | mod.city = null 207 | mod.aircraftType = null 208 | break 209 | case [ModType.LANDMARK, ModType.CITY, ModType.LANDSCAPE, ModType.LANDSCAPE_FIX]: 210 | mod.icao = null 211 | mod.aircraftType = null 212 | break 213 | case [ModType.LIVRERY, ModType.AIRCRAFT_MODEL]: 214 | mod.city = null 215 | mod.icao = null 216 | break 217 | default: 218 | mod.city = null 219 | mod.icao = null 220 | mod.aircraftType = null 221 | } 222 | } 223 | 224 | try { 225 | Mods.instance.addAllMods(mods, true) 226 | } 227 | catch (ModsParseException.MultipleModsCheckException ex) { 228 | checkErrorsByMod = ex.checkErrorsByMod 229 | updateMessagePanels() 230 | changeType() 231 | frame.revalidate() 232 | frame.repaint() 233 | return 234 | } 235 | 236 | Mods.instance.saveToTxt() 237 | 238 | SwingUtil.closeWindow(frame); 239 | Main.restart(); 240 | } 241 | 242 | /* 243 | * We would like to have linked messagePanel's visibilities dynamically to the respective checkErrors 244 | * (as we do dynamic linking in MessageLabel and WarnLightLabel), but this doesn't work because 245 | * (a) we want to actually remove / add components completely (to also erase the space occupied by them) 246 | * which cannot be done in this "dymnamic" fashion and (b) even if we used the #visible flag instead of 247 | * actually removing / adding them, even a revalidate() / repaint() wouldn't actually properly show them 248 | * since these methods don't properly update the UI in case of shifting components around. 249 | */ 250 | public void updateMessagePanels() { 251 | messagePanels.each { 252 | if (it.parent != null) { 253 | it.parent.remove(it) 254 | } 255 | if (isVisible(it)) { 256 | it.addBack() 257 | } 258 | } 259 | } 260 | 261 | private boolean isVisible(MessagePanel panel) { 262 | Mod.CheckErrors checkErrors = getCurrentCheckErrors() 263 | 264 | if (checkErrors == null) return false 265 | 266 | return panel.properties.any { checkErrors.getErrorsByField().containsKey(it) } 267 | } 268 | 269 | public Mod.CheckErrors getCurrentCheckErrors() { 270 | if (mod == null) return null 271 | 272 | return checkErrorsByMod[mod.name] 273 | } 274 | 275 | public void updateModsDb() { 276 | SwingUtil.closeWindow(frame); 277 | 278 | if (ModsDb.instance.update() { 279 | Main.restart(); 280 | }) { 281 | if (!ModsDb.UPDATE_WITH_GIT) { 282 | Dialogs.updateModsDbSuccessful(); 283 | } 284 | else { 285 | return // ModsDb.instance.update() will take control. 286 | } 287 | } 288 | Main.restart(); 289 | } 290 | } 291 | 292 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/ExampleComboBox.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit 2 | 3 | import groovy.transform.CompileStatic 4 | import java.awt.Component 5 | import java.awt.Font 6 | import java.awt.event.FocusAdapter 7 | import java.awt.event.FocusEvent 8 | import java.awt.event.KeyAdapter 9 | import java.awt.event.KeyEvent 10 | import javax.swing.JComboBox 11 | import javax.swing.JTextField 12 | 13 | @CompileStatic 14 | class ExampleComboBox extends JComboBox implements HasText { 15 | private String example 16 | 17 | public ExampleComboBox(String example) { 18 | super([example] as T[]) 19 | this.example = example 20 | 21 | // add focus listener as in https://stackoverflow.com/a/17285132 22 | Component component = getEditor().getEditorComponent(); 23 | if (component instanceof JTextField) { 24 | JTextField textField = (JTextField) component; 25 | 26 | textField.addFocusListener(new FocusAdapter() { 27 | @Override 28 | public void focusLost(FocusEvent e) { 29 | super.focusLost(e); 30 | 31 | if (editable) { 32 | setText(getText().toString().replaceAll("\t", " ")); 33 | } 34 | 35 | if ("".equals(getText())) { 36 | setText(null) 37 | } 38 | } 39 | 40 | @Override 41 | public void focusGained(FocusEvent e) { 42 | super.focusGained(e); 43 | if (getText() == null) { // null == example item, see below 44 | editor.item = "" 45 | } 46 | } 47 | }); 48 | 49 | HasText.addKeyListener(textField, this); 50 | } 51 | } 52 | 53 | public T getText() { 54 | T item 55 | if (editable) { 56 | item = editor.item 57 | } 58 | else { 59 | item = selectedItem 60 | } 61 | 62 | if (example.equals(item)) { 63 | return null 64 | } 65 | else { 66 | return item 67 | } 68 | } 69 | 70 | public void setText(T item) { 71 | if (item == null || "".equals(item)) { 72 | item = example 73 | } 74 | 75 | if (editable) { 76 | editor.item = item 77 | } 78 | else { 79 | selectedItem = item 80 | } 81 | } 82 | 83 | public void removeAllItems() { 84 | super.removeAllItems() 85 | addItem(example) 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/ExampleTextField.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit; 2 | 3 | import java.awt.Font; 4 | import java.awt.event.FocusAdapter; 5 | import java.awt.event.FocusEvent; 6 | import java.awt.event.KeyAdapter; 7 | import java.awt.event.KeyEvent; 8 | import javax.swing.JTextField; 9 | 10 | import msfsmodmanager.util.StringUtil; 11 | 12 | public class ExampleTextField extends JTextField implements HasText { 13 | 14 | String exampleText; 15 | Font defaultFont; 16 | 17 | public ExampleTextField(String exampleText) { 18 | this.exampleText = exampleText; 19 | this.defaultFont = getFont(); 20 | setToExampleText(); 21 | 22 | this.addFocusListener(new FocusAdapter() { 23 | @Override 24 | public void focusLost(FocusEvent e) { 25 | super.focusLost(e); 26 | 27 | setText(getText().replaceAll("\t", " ")); 28 | 29 | if ("".equals(getText())) { 30 | setToExampleText(); 31 | } 32 | } 33 | 34 | @Override 35 | public void focusGained(FocusEvent e) { 36 | super.focusGained(e); 37 | if (getText() == null) { // null == example text, see below 38 | setToCustomText(""); 39 | } 40 | } 41 | }); 42 | 43 | HasText.addKeyListener(this, this); 44 | } 45 | 46 | @Override 47 | public String getText() { 48 | String ret = super.getText(); 49 | if (exampleText.equals(ret)) { 50 | return null; 51 | } else { 52 | return ret; 53 | } 54 | } 55 | 56 | @Override 57 | public void setText(String text) { 58 | if (text == null || "".equals(text)) { 59 | setToExampleText(); 60 | } else if (!exampleText.equals(text)) { 61 | setToCustomText(text); 62 | } 63 | } 64 | 65 | private void setToExampleText() { 66 | super.setText(exampleText); 67 | setForeground(new java.awt.Color(153, 153, 153)); 68 | super.setFont(defaultFont.deriveFont(defaultFont.getStyle() | Font.ITALIC)); 69 | } 70 | 71 | private void setToCustomText(String text) { 72 | super.setText(text); 73 | setForeground(new java.awt.Color(0, 0, 0)); 74 | super.setFont(defaultFont); 75 | } 76 | 77 | @Override 78 | public void setFont(Font f) { 79 | super.setFont(f); 80 | this.defaultFont = f; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/HasText.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit; 2 | 3 | import java.awt.event.KeyAdapter; 4 | import java.awt.event.KeyEvent; 5 | import javax.swing.JTextField; 6 | 7 | import msfsmodmanager.util.StringUtil; 8 | 9 | public interface HasText { 10 | public T getText(); 11 | 12 | public void setText(T text); 13 | 14 | public static void addKeyListener(JTextField textField, HasText hasText) { 15 | textField.addKeyListener(new KeyAdapter() { 16 | public void keyReleased(KeyEvent e) { 17 | StringUtil.Trim trim = StringUtil.canTrim(hasText.getText().toString()); 18 | if (trim != null) { 19 | hasText.setText((T) hasText.getText().toString().trim()); 20 | if (trim == StringUtil.Trim.START) { 21 | textField.setCaretPosition(0); 22 | } 23 | else if (trim == StringUtil.Trim.END) { 24 | textField.setCaretPosition(hasText.getText().toString().length()); 25 | } 26 | else throw new IllegalArgumentException(trim.toString()); 27 | } 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/MessageLabel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit; 2 | 3 | import javax.swing.JLabel; 4 | 5 | import msfsmodmanager.model.Mod; 6 | import msfsmodmanager.model.FirstDefinition; 7 | import msfsmodmanager.ui.I18N; 8 | 9 | public class MessageLabel extends JLabel { 10 | private static final String MISSING_TEXT = "Must not be empty."; 11 | private static final String COUNTRY_TO_CONTINENT_CONSISTENCY_TEXT = "Inconsistent country link: ${country} has been previously linked to ${continent}."; 12 | private static final String CITY_TO_COUNTRY_CONSISTENCY_TEXT = "Inconsistent city link: ${city} has been previously linked to ${country}."; 13 | private static final String CITY_TO_CONTINENT_CONSISTENCY_TEXT = "Inconsistent city link: ${city} has been previously linked to ${continent}."; 14 | 15 | EditModsFrameHandler handler; 16 | String property; 17 | 18 | private String illegalText; 19 | 20 | public MessageLabel(EditModsFrameHandler handler, String property) { 21 | this(handler, property, "Must be a valid text."); 22 | } 23 | 24 | public MessageLabel(EditModsFrameHandler handler, String property, String illegalText) { 25 | this.handler = handler; 26 | this.property = property; 27 | this.illegalText = illegalText; 28 | 29 | setFont(new java.awt.Font("Tahoma", 1, 11)); 30 | setForeground(java.awt.Color.red); 31 | setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 18, 2, 8)); 32 | } 33 | 34 | @Override 35 | public String getText() { 36 | if (handler == null) return ""; // Additional safeguard is needed during construction init 37 | 38 | Mod.CheckErrors checkErrors = handler.getCurrentCheckErrors(); 39 | 40 | if (checkErrors == null) return ""; 41 | 42 | return buildText(checkErrors.getErrorsByField().get(property)); 43 | } 44 | 45 | private String buildText(Mod.CheckErrorInfo info) { 46 | Mod.CheckErrorType type = info.getT(); 47 | FirstDefinition payload = info.getPayload(); 48 | 49 | if (type == null) return ""; 50 | 51 | switch(type) { 52 | case MISSING: return MISSING_TEXT; 53 | case ILLEGAL: return illegalText; 54 | case COUNTRY_TO_CONTINENT_CONSISTENCY: return replaceVariables(COUNTRY_TO_CONTINENT_CONSISTENCY_TEXT, payload); 55 | case CITY_TO_COUNTRY_CONSISTENCY: return replaceVariables(CITY_TO_COUNTRY_CONSISTENCY_TEXT, payload); 56 | case CITY_TO_CONTINENT_CONSISTENCY: return replaceVariables(CITY_TO_CONTINENT_CONSISTENCY_TEXT, payload); 57 | 58 | default: throw new IllegalArgumentException(type.toString()); 59 | } 60 | } 61 | 62 | private static String replaceVariables(String input, FirstDefinition payload) { 63 | return input 64 | .replaceAll("\\$\\{city\\}", payload.getMod().getCity()) 65 | .replaceAll("\\$\\{country\\}", !("".equals(payload.getMod().getCountry())) ? I18N.getString("Country." + payload.getMod().getCountry()) : "(none)") 66 | .replaceAll("\\$\\{continent\\}", payload.getMod().getContinent() != null ? payload.getMod().getContinent().toString() : "(none)"); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/MessagePanel.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit 2 | 3 | import groovy.transform.CompileStatic 4 | import javax.swing.JComponent 5 | import javax.swing.JPanel 6 | 7 | @CompileStatic 8 | public class MessagePanel extends JPanel { 9 | public final String[] properties; 10 | 11 | /* 12 | * We need to keep these components in memory permanently because when we do a parentComponent.remove(this) 13 | * later (which we will!), the link through #getParent() doesn't work anymore thus an "add back" would 14 | * run into a NullPointer when working with #getParent(). 15 | */ 16 | public JComponent parentComponent; 17 | public JComponent previousComponent; 18 | 19 | public MessagePanel(String... properties) { 20 | this.properties = properties; 21 | } 22 | 23 | public void addBack() { 24 | int previousIndex = parentComponent.getComponents().findIndexOf { it == previousComponent } 25 | parentComponent.add(this, previousIndex+1) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/edit/WarnLightLabel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.edit; 2 | 3 | import javax.swing.JLabel; 4 | 5 | import msfsmodmanager.model.Mod; 6 | 7 | public class WarnLightLabel extends JLabel { 8 | EditModsFrameHandler handler; 9 | String property; 10 | 11 | public WarnLightLabel(EditModsFrameHandler handler, String property) { 12 | this.handler = handler; 13 | this.property = property; 14 | 15 | setBackground(new java.awt.Color(255, 51, 51)); 16 | setText(" "); 17 | setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1)); 18 | setOpaque(true); 19 | } 20 | 21 | @Override 22 | public boolean isVisible() { 23 | Mod.CheckErrors checkErrors = handler.getCurrentCheckErrors(); 24 | 25 | if (checkErrors == null) return false; 26 | 27 | return checkErrors.getErrorsByField().get(property).getT() != null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/error/ErrorFrame.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/error/ErrorFrame.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.error; 2 | 3 | import java.awt.Font; 4 | import java.awt.event.WindowAdapter; 5 | import java.awt.event.WindowEvent; 6 | import java.awt.font.TextAttribute; 7 | import java.util.Arrays; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import javax.swing.JDialog; 11 | import javax.swing.JFrame; 12 | 13 | import msfsmodmanager.Main; 14 | import msfsmodmanager.logic.ErrorHandler; 15 | import msfsmodmanager.util.Browser; 16 | import msfsmodmanager.util.SwingUtil; 17 | import msfsmodmanager.logic.ModDeleter; 18 | import msfsmodmanager.state.ModsDb; 19 | import static msfsmodmanager.logic.ErrorHandler.ErrorType; 20 | 21 | 22 | public class ErrorFrame extends javax.swing.JDialog { 23 | private static final Font LINK_FONT; 24 | 25 | private String message; 26 | private String details; 27 | private String stackTrace; 28 | private ErrorType errorType; 29 | private boolean forceShutdown; 30 | 31 | private final ErrorFrameHandler handler = new ErrorFrameHandler(this); 32 | 33 | static { 34 | Map fontAttributes = new HashMap(); 35 | fontAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON); 36 | LINK_FONT = new java.awt.Font("Tahoma", 1, 11).deriveFont(fontAttributes); 37 | } 38 | 39 | /** 40 | * Creates new form ErrorFrame 41 | */ 42 | public ErrorFrame(String title, String message, String details, String stackTrace, ErrorType errorType, boolean forceShutdown) { 43 | super((JFrame)null, true); 44 | setTitle("MSFS ModSelector: " + title); 45 | this.message = message; 46 | this.details = details; 47 | this.stackTrace = stackTrace; 48 | this.errorType = errorType; 49 | this.forceShutdown = forceShutdown; 50 | 51 | initComponents(); 52 | SwingUtil.initPanel(this, quitButton); 53 | 54 | addWindowListener(new WindowAdapter() { 55 | public void windowClosing(WindowEvent evt) { 56 | if (forceShutdown) { 57 | System.exit(1); 58 | } 59 | else { 60 | SwingUtil.closeWindow(ErrorFrame.this); 61 | } 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * This method is called from within the constructor to initialize the form. 68 | * WARNING: Do NOT modify this code. The content of this method is always 69 | * regenerated by the Form Editor. 70 | */ 71 | @SuppressWarnings("unchecked") 72 | // //GEN-BEGIN:initComponents 73 | private void initComponents() { 74 | 75 | stacktracePanel = new javax.swing.JPanel(); 76 | if (stackTrace == null) { 77 | stacktracePanel.setVisible(false); 78 | } 79 | stacktraceInfoPanel = new javax.swing.JPanel(); 80 | stacktraceInfoLabel1 = new javax.swing.JLabel(); 81 | stacktraceInfoLabel2 = new javax.swing.JLabel(); 82 | stacktraceInfoLabel3 = new javax.swing.JLabel(); 83 | stacktraceInfoLabel4 = new javax.swing.JLabel(); 84 | stacktraceInfoLabel5 = new javax.swing.JLabel(); 85 | stacktraceScrollPane = new javax.swing.JScrollPane(); 86 | stacktraceTextArea = new javax.swing.JTextArea(); 87 | topPanel = new javax.swing.JPanel(); 88 | messageLabel = new javax.swing.JLabel(); 89 | detailsScrollPane = new javax.swing.JScrollPane(); 90 | bottomPanel = new javax.swing.JPanel(); 91 | duplicatesDeleteButton = new javax.swing.JButton(); 92 | updateModDbButton = new javax.swing.JButton(); 93 | autoAddModsButton = new javax.swing.JButton(); 94 | quitButton = new javax.swing.JButton(); 95 | 96 | setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); 97 | setPreferredSize(new java.awt.Dimension(723, 587)); 98 | getContentPane().setLayout(new java.awt.BorderLayout(0, 4)); 99 | 100 | stacktracePanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(2, 2, 2, 2)); 101 | stacktracePanel.setLayout(new java.awt.BorderLayout()); 102 | 103 | stacktraceInfoPanel.setPreferredSize(new java.awt.Dimension(681, 30)); 104 | stacktraceInfoPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT, 0, 0)); 105 | 106 | stacktraceInfoLabel1.setText("If you believe this is a bug, please report it on "); 107 | stacktraceInfoPanel.add(stacktraceInfoLabel1); 108 | 109 | stacktraceInfoLabel2.setFont(LINK_FONT); 110 | stacktraceInfoLabel2.setForeground(new java.awt.Color(0, 0, 255)); 111 | stacktraceInfoLabel2.setText("the project's GitHub page"); 112 | stacktraceInfoLabel2.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); 113 | stacktraceInfoLabel2.addMouseListener(new java.awt.event.MouseAdapter() { 114 | public void mouseReleased(java.awt.event.MouseEvent evt) { 115 | stacktraceInfoLabel2MouseReleased(evt); 116 | } 117 | }); 118 | stacktraceInfoPanel.add(stacktraceInfoLabel2); 119 | 120 | stacktraceInfoLabel3.setText(" with the following information. Make sure to read the "); 121 | stacktraceInfoPanel.add(stacktraceInfoLabel3); 122 | 123 | stacktraceInfoLabel4.setFont(LINK_FONT); 124 | stacktraceInfoLabel4.setForeground(new java.awt.Color(0, 0, 255)); 125 | stacktraceInfoLabel4.setText("README"); 126 | stacktraceInfoLabel4.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); 127 | stacktraceInfoLabel4.addMouseListener(new java.awt.event.MouseAdapter() { 128 | public void mouseReleased(java.awt.event.MouseEvent evt) { 129 | stacktraceInfoLabel4MouseReleased(evt); 130 | } 131 | }); 132 | stacktraceInfoPanel.add(stacktraceInfoLabel4); 133 | 134 | stacktraceInfoLabel5.setText(" first."); 135 | stacktraceInfoPanel.add(stacktraceInfoLabel5); 136 | 137 | stacktracePanel.add(stacktraceInfoPanel, java.awt.BorderLayout.NORTH); 138 | 139 | stacktraceTextArea.setColumns(20); 140 | stacktraceTextArea.setRows(5); 141 | stacktraceTextArea.setText(stackTrace); 142 | stacktraceScrollPane.setViewportView(stacktraceTextArea); 143 | 144 | stacktracePanel.add(stacktraceScrollPane, java.awt.BorderLayout.CENTER); 145 | 146 | getContentPane().add(stacktracePanel, java.awt.BorderLayout.CENTER); 147 | 148 | topPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(2, 2, 2, 2)); 149 | topPanel.setLayout(new java.awt.BorderLayout(0, 2)); 150 | 151 | messageLabel.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N 152 | messageLabel.setText(message); 153 | topPanel.add(messageLabel, java.awt.BorderLayout.NORTH); 154 | 155 | detailsTextArea.setColumns(20); 156 | detailsTextArea.setRows(5); 157 | detailsTextArea.setText(details); 158 | detailsScrollPane.setViewportView(detailsTextArea); 159 | 160 | topPanel.add(detailsScrollPane, java.awt.BorderLayout.CENTER); 161 | detailsScrollPane.setVisible(details != null); 162 | 163 | getContentPane().add(topPanel, java.awt.BorderLayout.NORTH); 164 | if (stackTrace == null) { 165 | getContentPane().remove(topPanel); 166 | getContentPane().add(topPanel, java.awt.BorderLayout.CENTER); 167 | } 168 | 169 | bottomPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); 170 | 171 | duplicatesDeleteButton.setText("DELETE the duplicates in the Temp folder NOW"); 172 | duplicatesDeleteButton.addActionListener(new java.awt.event.ActionListener() { 173 | public void actionPerformed(java.awt.event.ActionEvent evt) { 174 | duplicatesDeleteButtonActionPerformed(evt); 175 | } 176 | }); 177 | bottomPanel.add(duplicatesDeleteButton); 178 | if (errorType != ErrorType.DUPLICATE_MODS) { 179 | duplicatesDeleteButton.setVisible(false); 180 | } 181 | 182 | updateModDbButton.setText("Search for new mod definitions online (Update mod DB)"); 183 | updateModDbButton.setToolTipText(ModsDb.instance.isUpdatable() ? null : "Can be updated every 10 minutes only."); 184 | updateModDbButton.setEnabled(ModsDb.instance.isUpdatable()); 185 | updateModDbButton.addActionListener(new java.awt.event.ActionListener() { 186 | public void actionPerformed(java.awt.event.ActionEvent evt) { 187 | updateModDbButtonActionPerformed(evt); 188 | } 189 | }); 190 | bottomPanel.add(updateModDbButton); 191 | if (errorType != ErrorType.UNREGISTERED_MODS) { 192 | updateModDbButton.setVisible(false); 193 | } 194 | 195 | autoAddModsButton.setText("Auto-add these mod definitions found online"); 196 | autoAddModsButton.addActionListener(new java.awt.event.ActionListener() { 197 | public void actionPerformed(java.awt.event.ActionEvent evt) { 198 | autoAddModsButtonActionPerformed(evt); 199 | } 200 | }); 201 | bottomPanel.add(autoAddModsButton); 202 | if (errorType != ErrorType.UNREGISTERED_MODS_IN_DB) { 203 | autoAddModsButton.setVisible(false); 204 | } 205 | 206 | quitButton.setText(forceShutdown ? "Quit" : "Close"); 207 | quitButton.addActionListener(new java.awt.event.ActionListener() { 208 | public void actionPerformed(java.awt.event.ActionEvent evt) { 209 | quitButtonActionPerformed(evt); 210 | } 211 | }); 212 | bottomPanel.add(quitButton); 213 | 214 | getContentPane().add(bottomPanel, java.awt.BorderLayout.SOUTH); 215 | 216 | pack(); 217 | }// //GEN-END:initComponents 218 | 219 | private void quitButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_quitButtonActionPerformed 220 | if (forceShutdown) { 221 | System.exit(1); 222 | } 223 | else { 224 | SwingUtil.closeWindow(this); 225 | } 226 | }//GEN-LAST:event_quitButtonActionPerformed 227 | 228 | private void stacktraceInfoLabel4MouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_stacktraceInfoLabel4MouseReleased 229 | Browser.openWebpage("https://github.com/captn-nick/MSFS-Mod-Selector"); 230 | }//GEN-LAST:event_stacktraceInfoLabel4MouseReleased 231 | 232 | private void stacktraceInfoLabel2MouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_stacktraceInfoLabel2MouseReleased 233 | Browser.openWebpage("https://github.com/captn-nick/MSFS-Mod-Selector/issues"); 234 | }//GEN-LAST:event_stacktraceInfoLabel2MouseReleased 235 | 236 | private void duplicatesDeleteButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_duplicatesDeleteButtonActionPerformed 237 | ModDeleter.deleteInTempDirectory(Arrays.asList( details.split("\n"))); 238 | SwingUtil.closeWindow(this); 239 | Main.restart(); 240 | }//GEN-LAST:event_duplicatesDeleteButtonActionPerformed 241 | 242 | private void autoAddModsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_autoAddModsButtonActionPerformed 243 | handler.autoAddMods(); 244 | }//GEN-LAST:event_autoAddModsButtonActionPerformed 245 | 246 | private void updateModDbButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_updateModDbButtonActionPerformed 247 | handler.updateModsDb(); 248 | }//GEN-LAST:event_updateModDbButtonActionPerformed 249 | 250 | public static void show(String title, String message, String details, String stackTrace, ErrorType errorType, boolean forceShutdown) { 251 | /* Set the Nimbus look and feel */ 252 | // 253 | /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. 254 | * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 255 | */ 256 | try { 257 | for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 258 | if ("Nimbus".equals(info.getName())) { 259 | javax.swing.UIManager.setLookAndFeel(info.getClassName()); 260 | break; 261 | } 262 | } 263 | } catch (ClassNotFoundException ex) { 264 | java.util.logging.Logger.getLogger(ErrorFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 265 | } catch (InstantiationException ex) { 266 | java.util.logging.Logger.getLogger(ErrorFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 267 | } catch (IllegalAccessException ex) { 268 | java.util.logging.Logger.getLogger(ErrorFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 269 | } catch (javax.swing.UnsupportedLookAndFeelException ex) { 270 | java.util.logging.Logger.getLogger(ErrorFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 271 | } 272 | // 273 | 274 | /* Create and display the form */ 275 | java.awt.EventQueue.invokeLater(new Runnable() { 276 | public void run() { 277 | new ErrorFrame(title, message, details, stackTrace, errorType, forceShutdown).setVisible(true); 278 | } 279 | }); 280 | } 281 | 282 | // Variables declaration - do not modify//GEN-BEGIN:variables 283 | private javax.swing.JButton autoAddModsButton; 284 | private javax.swing.JPanel bottomPanel; 285 | private javax.swing.JScrollPane detailsScrollPane; 286 | public final javax.swing.JTextArea detailsTextArea = new javax.swing.JTextArea(); 287 | private javax.swing.JButton duplicatesDeleteButton; 288 | private javax.swing.JLabel messageLabel; 289 | private javax.swing.JButton quitButton; 290 | private javax.swing.JLabel stacktraceInfoLabel1; 291 | private javax.swing.JLabel stacktraceInfoLabel2; 292 | private javax.swing.JLabel stacktraceInfoLabel3; 293 | private javax.swing.JLabel stacktraceInfoLabel4; 294 | private javax.swing.JLabel stacktraceInfoLabel5; 295 | private javax.swing.JPanel stacktraceInfoPanel; 296 | private javax.swing.JPanel stacktracePanel; 297 | private javax.swing.JScrollPane stacktraceScrollPane; 298 | private javax.swing.JTextArea stacktraceTextArea; 299 | private javax.swing.JPanel topPanel; 300 | private javax.swing.JButton updateModDbButton; 301 | // End of variables declaration//GEN-END:variables 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/error/ErrorFrameHandler.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.error 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.Main 5 | import msfsmodmanager.state.Mods 6 | import msfsmodmanager.state.ModsDb 7 | import msfsmodmanager.util.SwingUtil 8 | 9 | @CompileStatic 10 | class ErrorFrameHandler { 11 | ErrorFrame frame 12 | 13 | public ErrorFrameHandler(ErrorFrame frame) { 14 | this.frame = frame 15 | } 16 | 17 | @Deprecated 18 | public void autoAddMods() { 19 | List lines = frame.detailsTextArea.text.split("\n") as List 20 | lines = lines.findAll { String line -> 21 | line.contains("\t") 22 | } 23 | Mods.instance.addAllMods(lines) 24 | Mods.instance.saveToTxt() 25 | 26 | SwingUtil.closeWindow(frame); 27 | Main.restart() 28 | } 29 | 30 | @Deprecated 31 | public void updateModsDb() { 32 | throw new UnsupportedOperationException("Use EditModsFrameHandler instead.") 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/git/GitFrame.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/git/GitFrame.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.git; 2 | 3 | import groovy.lang.Closure; 4 | import java.awt.Color; 5 | import java.awt.event.WindowAdapter; 6 | import java.awt.event.WindowEvent; 7 | import javax.swing.JDialog; 8 | import javax.swing.JFrame; 9 | import msfsmodmanager.util.SwingUtil; 10 | 11 | public class GitFrame extends javax.swing.JDialog { 12 | boolean error; 13 | private String out; 14 | 15 | private Closure okButtonAction; 16 | 17 | /** 18 | * Creates new form GitFrame 19 | */ 20 | public GitFrame(boolean error, String out, Closure okButtonAction) { 21 | super((JFrame)null, true); 22 | this.error = error; 23 | this.out = out; 24 | this.okButtonAction = okButtonAction; 25 | 26 | initComponents(); 27 | SwingUtil.initPanel(this, okButton); 28 | 29 | addWindowListener(new WindowAdapter() { 30 | public void windowClosing(WindowEvent evt) { 31 | closeWindow(); 32 | } 33 | }); 34 | } 35 | 36 | private void closeWindow() { 37 | SwingUtil.closeWindow(this); 38 | okButtonAction.call(); 39 | } 40 | 41 | /** 42 | * This method is called from within the constructor to initialize the form. 43 | * WARNING: Do NOT modify this code. The content of this method is always 44 | * regenerated by the Form Editor. 45 | */ 46 | @SuppressWarnings("unchecked") 47 | // //GEN-BEGIN:initComponents 48 | private void initComponents() { 49 | 50 | scrollPane = new javax.swing.JScrollPane(); 51 | textArea = new javax.swing.JTextArea(); 52 | label = new javax.swing.JLabel(); 53 | buttonPanel = new javax.swing.JPanel(); 54 | okButton = new javax.swing.JButton(); 55 | 56 | setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); 57 | setTitle(error ? "Git update: Error" : "Git update"); 58 | 59 | textArea.setColumns(20); 60 | textArea.setRows(5); 61 | textArea.setText(out); 62 | scrollPane.setViewportView(textArea); 63 | 64 | getContentPane().add(scrollPane, java.awt.BorderLayout.CENTER); 65 | 66 | label.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N 67 | label.setForeground(error ? new Color(255, 0, 0) : new Color(0, 0, 0)); 68 | label.setText(error ? "Encountered the following error during a Git pull:" : "Git pull successful."); 69 | label.setBorder(javax.swing.BorderFactory.createEmptyBorder(4, 4, 4, 4)); 70 | getContentPane().add(label, java.awt.BorderLayout.NORTH); 71 | 72 | buttonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); 73 | 74 | okButton.setText("OK"); 75 | okButton.addActionListener(new java.awt.event.ActionListener() { 76 | public void actionPerformed(java.awt.event.ActionEvent evt) { 77 | okButtonActionPerformed(evt); 78 | } 79 | }); 80 | buttonPanel.add(okButton); 81 | 82 | getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH); 83 | 84 | pack(); 85 | }// //GEN-END:initComponents 86 | 87 | private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed 88 | closeWindow(); 89 | }//GEN-LAST:event_okButtonActionPerformed 90 | 91 | /** 92 | * @param args the command line arguments 93 | */ 94 | public static void show(boolean error, String out, Closure okButtonAction) { 95 | /* Set the Nimbus look and feel */ 96 | // 97 | /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. 98 | * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 99 | */ 100 | try { 101 | for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 102 | if ("Nimbus".equals(info.getName())) { 103 | javax.swing.UIManager.setLookAndFeel(info.getClassName()); 104 | break; 105 | } 106 | } 107 | } catch (ClassNotFoundException ex) { 108 | java.util.logging.Logger.getLogger(GitFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 109 | } catch (InstantiationException ex) { 110 | java.util.logging.Logger.getLogger(GitFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 111 | } catch (IllegalAccessException ex) { 112 | java.util.logging.Logger.getLogger(GitFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 113 | } catch (javax.swing.UnsupportedLookAndFeelException ex) { 114 | java.util.logging.Logger.getLogger(GitFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 115 | } 116 | // 117 | 118 | /* Create and display the form */ 119 | java.awt.EventQueue.invokeLater(new Runnable() { 120 | public void run() { 121 | new GitFrame(error, out, okButtonAction).setVisible(true); 122 | } 123 | }); 124 | } 125 | 126 | // Variables declaration - do not modify//GEN-BEGIN:variables 127 | private javax.swing.JPanel buttonPanel; 128 | private javax.swing.JLabel label; 129 | private javax.swing.JButton okButton; 130 | private javax.swing.JScrollPane scrollPane; 131 | private javax.swing.JTextArea textArea; 132 | // End of variables declaration//GEN-END:variables 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/AircraftPanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/AircraftPanel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import msfsmodmanager.model.AircraftType; 7 | import msfsmodmanager.ui.I18N; 8 | 9 | public class AircraftPanel extends javax.swing.JPanel { 10 | 11 | /** 12 | * Creates new form AircraftPanel 13 | */ 14 | public AircraftPanel() { 15 | initComponents(); 16 | } 17 | 18 | public List getSelection() { 19 | List ret = new ArrayList<>(); 20 | if (airlinerCheckBox.isSelected()) ret.add(AircraftType.AIRLINER); 21 | if (jetCheckBox.isSelected()) ret.add(AircraftType.JET); 22 | if (turbopropCheckBox.isSelected()) ret.add(AircraftType.TURBOPROP); 23 | if (propellerCheckBox.isSelected()) ret.add(AircraftType.PROPELLER); 24 | return ret; 25 | } 26 | 27 | /** 28 | * This method is called from within the constructor to initialize the form. 29 | * WARNING: Do NOT modify this code. The content of this method is always 30 | * regenerated by the Form Editor. 31 | */ 32 | @SuppressWarnings("unchecked") 33 | // //GEN-BEGIN:initComponents 34 | private void initComponents() { 35 | 36 | setBorder(javax.swing.BorderFactory.createTitledBorder(I18N.getString("AircraftType"))); 37 | setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.PAGE_AXIS)); 38 | 39 | airlinerCheckBox.setSelected(true); 40 | airlinerCheckBox.setText(I18N.getString("AircraftType.AL")); 41 | add(airlinerCheckBox); 42 | 43 | jetCheckBox.setSelected(true); 44 | jetCheckBox.setText(I18N.getString("AircraftType.JT")); 45 | add(jetCheckBox); 46 | 47 | turbopropCheckBox.setSelected(true); 48 | turbopropCheckBox.setText(I18N.getString("AircraftType.TP")); 49 | add(turbopropCheckBox); 50 | 51 | propellerCheckBox.setSelected(true); 52 | propellerCheckBox.setText(I18N.getString("AircraftType.PR")); 53 | add(propellerCheckBox); 54 | }// //GEN-END:initComponents 55 | 56 | 57 | // Variables declaration - do not modify//GEN-BEGIN:variables 58 | public final javax.swing.JCheckBox airlinerCheckBox = new javax.swing.JCheckBox(); 59 | public final javax.swing.JCheckBox jetCheckBox = new javax.swing.JCheckBox(); 60 | public final javax.swing.JCheckBox propellerCheckBox = new javax.swing.JCheckBox(); 61 | public final javax.swing.JCheckBox turbopropCheckBox = new javax.swing.JCheckBox(); 62 | // End of variables declaration//GEN-END:variables 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/CheckBoxTitledBorder.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.awt.Component; 4 | import java.awt.Container; 5 | import java.awt.Dimension; 6 | import java.awt.Graphics; 7 | import java.awt.Insets; 8 | import java.awt.Point; 9 | import java.awt.Rectangle; 10 | import java.awt.event.ActionListener; 11 | import java.awt.event.MouseAdapter; 12 | import java.awt.event.MouseEvent; 13 | import javax.swing.BorderFactory; 14 | import javax.swing.JCheckBox; 15 | import javax.swing.SwingConstants; 16 | import javax.swing.SwingUtilities; 17 | import javax.swing.border.AbstractBorder; 18 | import javax.swing.border.TitledBorder; 19 | 20 | // from https://stackoverflow.com/a/42024251 21 | public class CheckBoxTitledBorder extends AbstractBorder { 22 | 23 | private final TitledBorder _parent; 24 | private final JCheckBox _checkBox; 25 | 26 | public CheckBoxTitledBorder(String title, boolean selected) { 27 | _parent = BorderFactory.createTitledBorder(" " + title); 28 | _checkBox = new JCheckBox("", selected); 29 | _checkBox.setHorizontalTextPosition(SwingConstants.LEFT); 30 | } 31 | 32 | public boolean isSelected() { 33 | return _checkBox.isSelected(); 34 | } 35 | 36 | public void addActionListener(ActionListener listener) { 37 | _checkBox.addActionListener(listener); 38 | } 39 | 40 | @Override 41 | public boolean isBorderOpaque() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 47 | Insets borderInsets = _parent.getBorderInsets(c); 48 | Insets insets = getBorderInsets(c); 49 | int temp = (insets.top - borderInsets.top) / 2; 50 | _parent.paintBorder(c, g, x, y + temp, width, height - temp); 51 | Dimension size = _checkBox.getPreferredSize(); 52 | final Rectangle rectangle = new Rectangle(5, 0, size.width, size.height); 53 | 54 | final Container container = (Container) c; 55 | container.addMouseListener(new MouseAdapter() { 56 | private void dispatchEvent(MouseEvent me) { 57 | if (rectangle.contains(me.getX(), me.getY())) { 58 | Point pt = me.getPoint(); 59 | pt.translate(-5, 0); 60 | _checkBox.setBounds(rectangle); 61 | _checkBox.dispatchEvent(new MouseEvent(_checkBox, me.getID(), 62 | me.getWhen(), me.getModifiers(), pt.x, pt.y, me.getClickCount(), me.isPopupTrigger(), me.getButton())); 63 | if (!_checkBox.isValid()) { 64 | container.repaint(); 65 | } 66 | } 67 | } 68 | 69 | public void mousePressed(MouseEvent me) { 70 | dispatchEvent(me); 71 | } 72 | 73 | public void mouseReleased(MouseEvent me) { 74 | dispatchEvent(me); 75 | } 76 | }); 77 | SwingUtilities.paintComponent(g, _checkBox, container, rectangle); 78 | } 79 | 80 | @Override 81 | public Insets getBorderInsets(Component c) { 82 | Insets insets = _parent.getBorderInsets(c); 83 | insets.top = Math.max(insets.top, _checkBox.getPreferredSize().height); 84 | return insets; 85 | } 86 | } -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/CheckBoxTitledBorderPanel.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/CheckBoxTitledBorderPanel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.util.List; 4 | import javax.swing.JComponent; 5 | import javax.swing.JLabel; 6 | 7 | public class CheckBoxTitledBorderPanel extends javax.swing.JPanel implements SelectableComp { 8 | private final String name; 9 | private final List selectableComponents; 10 | 11 | public CheckBoxTitledBorderPanel(String name, String title, List components) { 12 | setBorder(new CheckBoxTitledBorder(title, false)); 13 | this.name = name; 14 | this.selectableComponents = components; 15 | initSelectableComponents(); 16 | if (selectableComponents.isEmpty()) { 17 | add(new JLabel(" ")); 18 | } 19 | initComponents(); 20 | } 21 | 22 | /** 23 | * This method is called from within the constructor to initialize the form. 24 | * WARNING: Do NOT modify this code. The content of this method is always 25 | * regenerated by the Form Editor. 26 | */ 27 | @SuppressWarnings("unchecked") 28 | // //GEN-BEGIN:initComponents 29 | private void initComponents() { 30 | 31 | setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.PAGE_AXIS)); 32 | }// //GEN-END:initComponents 33 | 34 | public List getSelectableComponents() { 35 | return selectableComponents; 36 | } 37 | 38 | private void initSelectableComponents() { 39 | for (C component : selectableComponents) { 40 | add(component); 41 | } 42 | } 43 | 44 | @Override 45 | public String getName() { 46 | return name; 47 | } 48 | 49 | @Override 50 | public boolean isSelected() { 51 | return getBorder().isSelected(); 52 | } 53 | 54 | public CheckBoxTitledBorder getBorder() { 55 | return (CheckBoxTitledBorder) super.getBorder(); 56 | } 57 | 58 | 59 | 60 | // Variables declaration - do not modify//GEN-BEGIN:variables 61 | // End of variables declaration//GEN-END:variables 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/ContinentPanel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.util.List; 4 | import javax.swing.JComponent; 5 | 6 | import msfsmodmanager.model.Continent; 7 | import msfsmodmanager.ui.I18N; 8 | 9 | public class ContinentPanel extends CheckBoxTitledBorderPanel { 10 | public ContinentPanel(Continent continent) { 11 | super(continent.abbr, I18N.getString("Continent." + continent.abbr), (List)SelectableComps.create(continent)); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/CountryPanel.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.util.List; 4 | import javax.swing.JComponent; 5 | import msfsmodmanager.ui.I18N; 6 | 7 | public class CountryPanel extends CheckBoxTitledBorderPanel{ 8 | public CountryPanel(String country) { 9 | super(country, I18N.getString("Country." + country), (List)SelectableComps.createForCities(country)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/MainFrame.form: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/MainFrame.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import msfsmodmanager.model.Continent; 4 | import msfsmodmanager.model.ModType; 5 | import msfsmodmanager.logic.ErrorHandler; 6 | import msfsmodmanager.ui.I18N; 7 | import msfsmodmanager.util.SwingUtil; 8 | 9 | public class MainFrame extends javax.swing.JFrame { 10 | public final ContinentPanel eu = new ContinentPanel(Continent.EU); 11 | public final ContinentPanel uc = new ContinentPanel(Continent.UC); 12 | public final ContinentPanel ma = new ContinentPanel(Continent.MA); 13 | public final ContinentPanel sa = new ContinentPanel(Continent.SA); 14 | public final ContinentPanel as = new ContinentPanel(Continent.AS); 15 | public final ContinentPanel af = new ContinentPanel(Continent.AF); 16 | public final ContinentPanel oc = new ContinentPanel(Continent.OC); 17 | public final ContinentPanel aa = new ContinentPanel(Continent.AA); 18 | public final ContinentPanel of = new ContinentPanel(Continent.OF); 19 | 20 | public final AircraftPanel aircraftPanel = new AircraftPanel(); 21 | 22 | private final MainFrameHandler handler = new MainFrameHandler(this); 23 | 24 | /** 25 | * Creates new form MainFrame 26 | */ 27 | public MainFrame() { 28 | initComponents(); 29 | SwingUtil.initPanel(this, applyButton); 30 | } 31 | 32 | /** 33 | * This method is called from within the constructor to initialize the form. 34 | * WARNING: Do NOT modify this code. The content of this method is always 35 | * regenerated by the Form Editor. 36 | */ 37 | @SuppressWarnings("unchecked") 38 | // //GEN-BEGIN:initComponents 39 | private void initComponents() { 40 | 41 | typePanel = new javax.swing.JPanel(); 42 | buttonPanel = new javax.swing.JPanel(); 43 | rightButtonPanel = new javax.swing.JPanel(); 44 | deactivateAllButton = new javax.swing.JButton(); 45 | applyButton = new javax.swing.JButton(); 46 | leftButtonPanel = new javax.swing.JPanel(); 47 | contributeToModsDbButton = new javax.swing.JButton(); 48 | mainScrollPane = new javax.swing.JScrollPane(); 49 | mainPanel = new javax.swing.JPanel(); 50 | continentsPanel1 = new javax.swing.JPanel(); 51 | continentsPanel1.add(eu); 52 | continentsPanel2 = new javax.swing.JPanel(); 53 | continentsPanel2.add(uc); 54 | continentsPanel2.add(ma); 55 | continentsPanel2.add(sa); 56 | continentsPanel3 = new javax.swing.JPanel(); 57 | continentsPanel3.add(as); 58 | continentsPanel3.add(af); 59 | continentsPanel4 = new javax.swing.JPanel(); 60 | continentsPanel4.add(oc); 61 | continentsPanel4.add(aa); 62 | continentsPanel4.add(of); 63 | continentsPanel4.add(aircraftPanel); 64 | 65 | setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); 66 | setTitle("MSFS Mod Selector"); 67 | 68 | airportCheckBox.setSelected(ModType.defaultSelection.contains(ModType.AIRPORT)); 69 | airportCheckBox.setText(I18N.getString("ModType.AP")); 70 | typePanel.add(airportCheckBox); 71 | 72 | landmarkCheckBox.setSelected(ModType.defaultSelection.contains(ModType.LANDMARK)); 73 | landmarkCheckBox.setText(I18N.getString("ModType.LM")); 74 | typePanel.add(landmarkCheckBox); 75 | 76 | cityCheckBox.setSelected(ModType.defaultSelection.contains(ModType.CITY)); 77 | cityCheckBox.setText(I18N.getString("ModType.CT")); 78 | typePanel.add(cityCheckBox); 79 | 80 | landscapeCheckBox.setSelected(ModType.defaultSelection.contains(ModType.LANDSCAPE)); 81 | landscapeCheckBox.setText(I18N.getString("ModType.LS")); 82 | typePanel.add(landscapeCheckBox); 83 | 84 | landscapeFixCheckBox.setSelected(ModType.defaultSelection.contains(ModType.LANDSCAPE_FIX)); 85 | landscapeFixCheckBox.setText(I18N.getString("ModType.LF")); 86 | typePanel.add(landscapeFixCheckBox); 87 | 88 | livreryCheckBox.setSelected(ModType.defaultSelection.contains(ModType.LIVRERY)); 89 | livreryCheckBox.setText(I18N.getString("ModType.AL")); 90 | typePanel.add(livreryCheckBox); 91 | 92 | aircraftModelCheckBox.setSelected(ModType.defaultSelection.contains(ModType.AIRCRAFT_MODEL)); 93 | aircraftModelCheckBox.setText(I18N.getString("ModType.AM")); 94 | typePanel.add(aircraftModelCheckBox); 95 | 96 | otherCheckBox.setSelected(ModType.defaultSelection.contains(ModType.OTHER)); 97 | otherCheckBox.setText(I18N.getString("ModType.OT")); 98 | typePanel.add(otherCheckBox); 99 | 100 | getContentPane().add(typePanel, java.awt.BorderLayout.NORTH); 101 | 102 | buttonPanel.setLayout(new java.awt.BorderLayout()); 103 | 104 | rightButtonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); 105 | 106 | deactivateAllButton.setText("Deactivate all"); 107 | deactivateAllButton.addActionListener(new java.awt.event.ActionListener() { 108 | public void actionPerformed(java.awt.event.ActionEvent evt) { 109 | deactivateAllButtonActionPerformed(evt); 110 | } 111 | }); 112 | rightButtonPanel.add(deactivateAllButton); 113 | 114 | applyButton.setText("Apply"); 115 | applyButton.addActionListener(new java.awt.event.ActionListener() { 116 | public void actionPerformed(java.awt.event.ActionEvent evt) { 117 | applyButtonActionPerformed(evt); 118 | } 119 | }); 120 | rightButtonPanel.add(applyButton); 121 | 122 | buttonPanel.add(rightButtonPanel, java.awt.BorderLayout.EAST); 123 | 124 | leftButtonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT)); 125 | 126 | contributeToModsDbButton.setText("Contribute to mods DB"); 127 | contributeToModsDbButton.setToolTipText("Export the new mod definitions you have added in order to upload them to the central repository."); 128 | contributeToModsDbButton.addActionListener(new java.awt.event.ActionListener() { 129 | public void actionPerformed(java.awt.event.ActionEvent evt) { 130 | contributeToModsDbButtonActionPerformed(evt); 131 | } 132 | }); 133 | leftButtonPanel.add(contributeToModsDbButton); 134 | 135 | buttonPanel.add(leftButtonPanel, java.awt.BorderLayout.WEST); 136 | 137 | getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH); 138 | 139 | mainPanel.setLayout(new java.awt.GridLayout(1, 5)); 140 | 141 | continentsPanel1.setLayout(new javax.swing.BoxLayout(continentsPanel1, javax.swing.BoxLayout.PAGE_AXIS)); 142 | mainPanel.add(continentsPanel1); 143 | 144 | continentsPanel2.setLayout(new javax.swing.BoxLayout(continentsPanel2, javax.swing.BoxLayout.PAGE_AXIS)); 145 | mainPanel.add(continentsPanel2); 146 | 147 | continentsPanel3.setLayout(new javax.swing.BoxLayout(continentsPanel3, javax.swing.BoxLayout.PAGE_AXIS)); 148 | mainPanel.add(continentsPanel3); 149 | 150 | continentsPanel4.setLayout(new javax.swing.BoxLayout(continentsPanel4, javax.swing.BoxLayout.PAGE_AXIS)); 151 | mainPanel.add(continentsPanel4); 152 | 153 | mainScrollPane.setViewportView(mainPanel); 154 | 155 | getContentPane().add(mainScrollPane, java.awt.BorderLayout.CENTER); 156 | 157 | pack(); 158 | }// //GEN-END:initComponents 159 | 160 | private void applyButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_applyButtonActionPerformed 161 | handler.apply(); 162 | }//GEN-LAST:event_applyButtonActionPerformed 163 | 164 | private void deactivateAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deactivateAllButtonActionPerformed 165 | handler.deactivateAll(); 166 | }//GEN-LAST:event_deactivateAllButtonActionPerformed 167 | 168 | private void contributeToModsDbButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_contributeToModsDbButtonActionPerformed 169 | try { 170 | handler.addUserDefinedModsToModsDb(); 171 | } 172 | catch (Exception ex) { 173 | ErrorHandler.handleGlobalError(ex, false); 174 | } 175 | }//GEN-LAST:event_contributeToModsDbButtonActionPerformed 176 | 177 | /** 178 | * @param args the command line arguments 179 | */ 180 | public static void main(String args[]) { 181 | /* Set the Nimbus look and feel */ 182 | // 183 | /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. 184 | * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 185 | */ 186 | try { 187 | for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 188 | if ("Nimbus".equals(info.getName())) { 189 | javax.swing.UIManager.setLookAndFeel(info.getClassName()); 190 | break; 191 | } 192 | } 193 | } catch (ClassNotFoundException ex) { 194 | java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 195 | } catch (InstantiationException ex) { 196 | java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 197 | } catch (IllegalAccessException ex) { 198 | java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 199 | } catch (javax.swing.UnsupportedLookAndFeelException ex) { 200 | java.util.logging.Logger.getLogger(MainFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); 201 | } 202 | // 203 | 204 | /* Create and display the form */ 205 | java.awt.EventQueue.invokeLater(new Runnable() { 206 | public void run() { 207 | new MainFrame().setVisible(true); 208 | } 209 | }); 210 | } 211 | 212 | // Variables declaration - do not modify//GEN-BEGIN:variables 213 | public final javax.swing.JCheckBox aircraftModelCheckBox = new javax.swing.JCheckBox(); 214 | public final javax.swing.JCheckBox airportCheckBox = new javax.swing.JCheckBox(); 215 | private javax.swing.JButton applyButton; 216 | private javax.swing.JPanel buttonPanel; 217 | public final javax.swing.JCheckBox cityCheckBox = new javax.swing.JCheckBox(); 218 | private javax.swing.JPanel continentsPanel1; 219 | private javax.swing.JPanel continentsPanel2; 220 | private javax.swing.JPanel continentsPanel3; 221 | private javax.swing.JPanel continentsPanel4; 222 | private javax.swing.JButton contributeToModsDbButton; 223 | private javax.swing.JButton deactivateAllButton; 224 | public final javax.swing.JCheckBox landmarkCheckBox = new javax.swing.JCheckBox(); 225 | public final javax.swing.JCheckBox landscapeCheckBox = new javax.swing.JCheckBox(); 226 | public final javax.swing.JCheckBox landscapeFixCheckBox = new javax.swing.JCheckBox(); 227 | private javax.swing.JPanel leftButtonPanel; 228 | public final javax.swing.JCheckBox livreryCheckBox = new javax.swing.JCheckBox(); 229 | private javax.swing.JPanel mainPanel; 230 | private javax.swing.JScrollPane mainScrollPane; 231 | public final javax.swing.JCheckBox otherCheckBox = new javax.swing.JCheckBox(); 232 | private javax.swing.JPanel rightButtonPanel; 233 | private javax.swing.JPanel typePanel; 234 | // End of variables declaration//GEN-END:variables 235 | } 236 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/MainFrameHandler.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.Main 5 | 6 | import msfsmodmanager.ui.* 7 | import msfsmodmanager.logic.* 8 | import msfsmodmanager.model.* 9 | 10 | @CompileStatic 11 | class MainFrameHandler { 12 | MainFrame frame; 13 | 14 | public MainFrameHandler(MainFrame frame) { 15 | this.frame = frame 16 | } 17 | 18 | public void apply() { 19 | Main.init() 20 | 21 | Selection selection = SelectableComps.getSelection([frame.eu, frame.uc, frame.ma, frame.sa, frame.as, frame.af, frame.oc, frame.aa, frame.of]) 22 | selection.aircraftTypes = frame.aircraftPanel.selection 23 | 24 | if (frame.airportCheckBox.selected) selection.types << ModType.AIRPORT 25 | if (frame.landmarkCheckBox.selected) selection.types << ModType.LANDMARK 26 | if (frame.cityCheckBox.selected) selection.types << ModType.CITY 27 | if (frame.landscapeCheckBox.selected) selection.types << ModType.LANDSCAPE 28 | if (frame.landscapeFixCheckBox.selected) selection.types << ModType.LANDSCAPE_FIX 29 | if (frame.livreryCheckBox.selected) selection.types << ModType.LIVRERY 30 | if (frame.aircraftModelCheckBox.selected) selection.types << ModType.AIRCRAFT_MODEL 31 | if (frame.otherCheckBox.selected) selection.types << ModType.OTHER 32 | 33 | ModActivator.activateMods(selection) 34 | } 35 | 36 | public void deactivateAll() { 37 | Main.init() 38 | 39 | ModActivator.deactivateAllMods(); 40 | } 41 | 42 | public void addUserDefinedModsToModsDb() { 43 | Main.init() 44 | 45 | ModsDbHandler.addUserDefinedModsToModsDb() 46 | Dialogs.afterContributedToModsDb() 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/SelectableCheckBox.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | import java.awt.Font; 4 | import java.awt.Insets; 5 | import javax.swing.JCheckBox; 6 | import msfsmodmanager.ui.I18N; 7 | 8 | public class SelectableCheckBox extends JCheckBox implements SelectableComp { 9 | private final String name; 10 | private final boolean isCity; 11 | 12 | public SelectableCheckBox(String name, boolean isCountry) { 13 | super(isCountry ? I18N.getString("Country." + name) : name); 14 | 15 | this.name = name; 16 | this.isCity = !isCountry; 17 | 18 | setMargin(new Insets(0, 5, 0, 0)); 19 | if (isCountry) { 20 | setFont(new Font(getFont().getName(), Font.BOLD, getFont().getSize())); 21 | } 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return name; 27 | } 28 | 29 | @Override 30 | public boolean isCity() { 31 | return this.isCity; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/SelectableComp.java: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main; 2 | 3 | public interface SelectableComp { 4 | public String getName(); 5 | public boolean isSelected(); 6 | public default boolean isCity() { return false; } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/ui/main/SelectableComps.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.ui.main 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | import msfsmodmanager.model.* 6 | 7 | @CompileStatic 8 | class SelectableComps { 9 | public static List create(Continent continent) { 10 | return createForCountries(continent) + createForCities(continent) 11 | } 12 | 13 | private static List createForCountries(Continent continent) { 14 | return continent.countries.collect { 15 | if (Cities.byCountry(it).empty) { 16 | return new SelectableCheckBox(it, true) 17 | } 18 | else { 19 | return new CountryPanel(it) 20 | } 21 | } 22 | } 23 | 24 | public static List createForCities(Continent continent) { 25 | return Cities.byContinent(continent).collect { String it -> 26 | new SelectableCheckBox(it, false) 27 | } 28 | } 29 | 30 | public static List createForCities(String country) { 31 | return (Cities.byCountry(country)).collect { 32 | new SelectableCheckBox(it, false) 33 | } 34 | } 35 | 36 | public static Selection getSelection(List panels) { 37 | Selection selection = new Selection(getSelectedContinents(panels)) 38 | 39 | panels.each { continentPanel -> 40 | selection.countries.addAll(getSelectedCountries(continentPanel)) 41 | continentPanel.selectableComponents.each { countryPanel -> 42 | if (countryPanel in CountryPanel) { 43 | // then it has cities 44 | selection.cities.addAll(getSelectedCities((CountryPanel)countryPanel)) 45 | } 46 | } 47 | selection.cities.addAll(getSelectedCities(continentPanel)) 48 | } 49 | 50 | return selection 51 | } 52 | 53 | public static List getSelectedContinents(List panels) { 54 | return panels.findAll { it.selected }*.name 55 | } 56 | 57 | public static List getSelectedCountries(ContinentPanel panel) { 58 | getSelected(panel, false) 59 | } 60 | 61 | public static List getSelectedCities(ContinentPanel panel) { 62 | getSelected(panel, true) 63 | } 64 | 65 | public static List getSelectedCities(CountryPanel panel) { 66 | getSelected(panel, true) 67 | } 68 | 69 | private static List getSelected(CheckBoxTitledBorderPanel panel, boolean city) { 70 | return (((List)(panel.selectableComponents)).findAll { 71 | SelectableComp it -> it.selected && it.city == city 72 | }).collect { 73 | it.name 74 | } 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/Browser.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileStatic 4 | import java.awt.Desktop 5 | 6 | @CompileStatic 7 | class Browser { 8 | public static void openWebpage(String url) { 9 | Desktop.getDesktop().browse(new URL(url).toURI()); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/CmdLine.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.TupleConstructor 5 | 6 | @CompileStatic 7 | class CmdLine { 8 | public static Result execute(String command) { 9 | StringBuilder sout = new StringBuilder() 10 | StringBuilder serr = new StringBuilder() 11 | Process proc = command.execute() 12 | proc.consumeProcessOutput(sout, serr) 13 | proc.waitFor() 14 | return new Result(sout.toString(), serr.toString()) 15 | } 16 | 17 | @TupleConstructor 18 | public static class Result { 19 | String sout 20 | String serr 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/Git.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileStatic 4 | import msfsmodmanager.ui.git.GitFrame 5 | 6 | @CompileStatic 7 | class Git { 8 | public static boolean pull(String filePath, Closure okButtonAction) { 9 | CmdLine.Result result = CmdLine.execute($/cmd.exe /c "cd "/$ + filePath + /" && git pull"/) 10 | 11 | if (result.serr) { 12 | info(true, result.serr, okButtonAction) 13 | return false 14 | } 15 | else { 16 | info(false, result.sout, okButtonAction) 17 | return true 18 | } 19 | } 20 | 21 | private static void info(boolean error, String out, Closure okButtonAction) { 22 | GitFrame.show(error, out, okButtonAction) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/StringUtil.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class StringUtil { 7 | public static String trimStart(String input) { 8 | return input.replaceAll("^\\s+", "") 9 | } 10 | 11 | public static String trimEnd(String input) { 12 | return input.replaceAll('\\s+$', "") 13 | } 14 | 15 | public static String toString(Throwable ex) { 16 | StringWriter sw = new StringWriter(); 17 | PrintWriter pw = new PrintWriter(sw); 18 | ex.printStackTrace(pw); 19 | String sStackTrace = sw.toString(); 20 | return sStackTrace 21 | } 22 | 23 | public static Trim canTrim(String input) { 24 | if (input == null) return null 25 | 26 | String trimmed = input.trim() 27 | 28 | if (trimmed == input) return null 29 | 30 | if (input.startsWith(trimmed)) { 31 | return Trim.END 32 | } 33 | else if (input.endsWith(trimmed)) { 34 | return Trim.START 35 | } 36 | else { 37 | // if trimmable at both ends, move to end 38 | return Trim.END 39 | } 40 | } 41 | 42 | public static enum Trim { 43 | START, 44 | END, 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/SwingUtil.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | import java.awt.Component 6 | import java.awt.Container 7 | import java.awt.Window 8 | import javax.swing.JButton 9 | import javax.swing.JComponent 10 | import javax.swing.RootPaneContainer 11 | 12 | @CompileStatic 13 | class SwingUtil { 14 | public static void closeWindow(Window window) { 15 | window.setVisible(false) 16 | window.dispose() 17 | } 18 | 19 | @CompileDynamic // because setDefaultButton() is only defined on getRootPane() subclasses 20 | public static void initPanel(RootPaneContainer window, JComponent defaultComponent) { 21 | window.setLocationRelativeTo(null); 22 | 23 | if (defaultComponent in JButton) { 24 | window.getRootPane().setDefaultButton(defaultComponent); 25 | } 26 | 27 | defaultComponent.requestFocus(); 28 | } 29 | 30 | public static JComponent getPreviousComponent(JComponent component) { 31 | Container parent = component.getParent() 32 | int index = parent.getComponents().findIndexOf { it == component } 33 | return (JComponent)(parent.getComponent(index-1)) 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/msfsmodmanager/util/WebReader.groovy: -------------------------------------------------------------------------------- 1 | package msfsmodmanager.util 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class WebReader { 7 | private static String BROWSER_CLIENT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" 8 | 9 | public static String readUrl(String url) { 10 | new URL(url).getText(connectTimeout: 10 * 1000, readTimeout: 10 * 1000, 11 | useCaches: true, allowUserInteraction: false, 12 | requestProperties: ['User-Agent': BROWSER_CLIENT_USER_AGENT]) 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/default_labels.properties: -------------------------------------------------------------------------------- 1 | ModType.AP=Airports 2 | ModType.LM=Landmarks 3 | ModType.CT=Cities 4 | ModType.LS=Landscapes 5 | ModType.LF=Landscape fixes 6 | ModType.AL=Livreries 7 | ModType.AM=Aircraft models 8 | ModType.OT=Other 9 | 10 | ModType.AP.dropdown=A - Airport 11 | ModType.LM.dropdown=L - Landmark 12 | ModType.CT.dropdown=C - City 13 | ModType.LS.dropdown=S - Landscape 14 | ModType.LF.dropdown=F - Landscape fix 15 | ModType.AL.dropdown=Y - Livrery 16 | ModType.AM.dropdown=M - Aircraft model 17 | ModType.OT.dropdown=O - Other 18 | 19 | AircraftType=Aircraft 20 | AircraftType.AL=Airliner 21 | AircraftType.JT=Jet 22 | AircraftType.TP=Turboprop 23 | AircraftType.PR=Propeller 24 | 25 | Continent.EU=Europe 26 | Continent.UC=USA / Canada 27 | Continent.MA=Middle America 28 | Continent.SA=South America 29 | Continent.AF=Africa 30 | Continent.AS=Asia 31 | Continent.OC=Oceania 32 | Continent.AA=Arctica/Antarctica 33 | Continent.OF=Other/Fictional 34 | 35 | Country.ABW=Aruba 36 | Country.AFG=Afghanistan 37 | Country.AGO=Angola 38 | Country.AIA=Anguilla 39 | # Country.ALA=Åland Islands: removed because it's part of FIN=Finland 40 | Country.ALB=Albania 41 | Country.AND=Andorra 42 | Country.ARE=United Arab Emirates 43 | Country.ARG=Argentina 44 | Country.ARM=Armenia 45 | Country.ASM=American Samoa 46 | Country.ATA=Antarctica 47 | Country.ATF=French Southern Territories 48 | Country.ATG=Antigua and Barbuda 49 | Country.AUS=Australia 50 | Country.AUT=Austria 51 | Country.AZE=Azerbaijan 52 | Country.BDI=Burundi 53 | Country.BEL=Belgium 54 | Country.BEN=Benin 55 | Country.BES=Bonaire, Sint Eustatius and Saba 56 | Country.BFA=Burkina Faso 57 | Country.BGD=Bangladesh 58 | Country.BGR=Bulgaria 59 | Country.BHR=Bahrain 60 | Country.BHS=Bahamas 61 | Country.BIH=Bosnia and Herzegovina 62 | Country.BLM=Saint Barthélemy 63 | Country.BLR=Belarus 64 | Country.BLZ=Belize 65 | Country.BMU=Bermuda 66 | # Country.BOL=Bolivia (Plurinational State of) 67 | Country.BOL=Bolivia 68 | Country.BRA=Brazil 69 | Country.BRB=Barbados 70 | Country.BRN=Brunei Darussalam 71 | Country.BTN=Bhutan 72 | Country.BVT=Bouvet Island 73 | Country.BWA=Botswana 74 | Country.CAF=Central African Republic 75 | Country.CAN=Canada 76 | Country.CCK=Cocos (Keeling) Islands 77 | Country.CHE=Switzerland 78 | Country.CHL=Chile 79 | Country.CHN=China 80 | Country.CIV=Côte d'Ivoire 81 | Country.CMR=Cameroon 82 | # Country.COD=Congo, Democratic Republic of the 83 | Country.COD=D.R. Congo 84 | Country.COG=Congo 85 | Country.COK=Cook Islands 86 | Country.COL=Colombia 87 | Country.COM=Comoros 88 | Country.CPV=Cabo Verde 89 | Country.CRI=Costa Rica 90 | Country.CUB=Cuba 91 | Country.CUW=Curaçao 92 | Country.CXR=Christmas Island 93 | Country.CYM=Cayman Islands 94 | Country.CYP=Cyprus 95 | Country.CZE=Czechia 96 | Country.DEU=Germany 97 | Country.DJI=Djibouti 98 | Country.DMA=Dominica 99 | Country.DNK=Denmark 100 | Country.DOM=Dominican Republic 101 | Country.DZA=Algeria 102 | Country.ECU=Ecuador 103 | Country.EGY=Egypt 104 | Country.ERI=Eritrea 105 | Country.ESH=Western Sahara 106 | Country.ESP=Spain 107 | Country.EST=Estonia 108 | Country.ETH=Ethiopia 109 | # includes ALA=Åland Islands 110 | Country.FIN=Finland 111 | Country.FJI=Fiji 112 | # Country.FLK=Falkland Islands (Malvinas) 113 | Country.FLK=Falkland Islands 114 | Country.FRA=France 115 | Country.FRO=Faroe Islands 116 | # Country.FSM=Micronesia (Federated States of) 117 | Country.FSM=Micronesia 118 | Country.GAB=Gabon 119 | # Country.GBR=United Kingdom of Great Britain and Northern Ireland 120 | # Note: includes IMN=Isle of Man. For geographical reasons, northern Ireland locations should be filed under Country.IRL=Ireland. 121 | Country.GBR=UK - mid/south 122 | Country.GEO=Georgia 123 | Country.GGY=Guernsey 124 | Country.GHA=Ghana 125 | Country.GIB=Gibraltar 126 | Country.GIN=Guinea 127 | Country.GLP=Guadeloupe 128 | Country.GMB=Gambia 129 | Country.GNB=Guinea-Bissau 130 | Country.GNQ=Equatorial Guinea 131 | Country.GRC=Greece 132 | Country.GRD=Grenada 133 | Country.GRL=Greenland 134 | Country.GTM=Guatemala 135 | Country.GUF=French Guiana 136 | Country.GUM=Guam 137 | Country.GUY=Guyana 138 | Country.HKG=Hong Kong 139 | Country.HMD=Heard Island and McDonald Islands 140 | Country.HND=Honduras 141 | Country.HRV=Croatia 142 | Country.HTI=Haiti 143 | Country.HUN=Hungary 144 | Country.IDN=Indonesia 145 | # Country.IMN=Isle of Man: removed because it's part of GBR 146 | Country.IND=India 147 | Country.IOT=British Indian Ocean Territory 148 | # Country.IRL=Ireland 149 | # Note: for geographical reasons, northern Ireland locations should be filed under Country.IRL=Ireland 150 | Country.IRL=Ireland / N. Ireland 151 | # Country.IRN=Iran (Islamic Republic of) 152 | Country.IRN=Iran 153 | Country.IRQ=Iraq 154 | Country.ISL=Iceland 155 | Country.ISR=Israel 156 | # includes VAT=Holy See 157 | Country.ITA=Italy 158 | Country.JAM=Jamaica 159 | Country.JEY=Jersey 160 | Country.JOR=Jordan 161 | Country.JPN=Japan 162 | Country.KAZ=Kazakhstan 163 | Country.KEN=Kenya 164 | Country.KGZ=Kyrgyzstan 165 | Country.KHM=Cambodia 166 | Country.KIR=Kiribati 167 | Country.KNA=Saint Kitts and Nevis 168 | # Country.KOR=Korea, Republic of 169 | Country.KOR=South Korea 170 | Country.KWT=Kuwait 171 | # Country.LAO=Lao People's Democratic Republic 172 | Country.LAO=Laos 173 | Country.LBN=Lebanon 174 | Country.LBR=Liberia 175 | Country.LBY=Libya 176 | Country.LCA=Saint Lucia 177 | Country.LIE=Liechtenstein 178 | Country.LKA=Sri Lanka 179 | Country.LSO=Lesotho 180 | Country.LTU=Lithuania 181 | Country.LUX=Luxembourg 182 | Country.LVA=Latvia 183 | Country.MAC=Macao 184 | # Country.MAF=Saint Martin (French part) + Country.SXM=Sint Maarten (Dutch part) 185 | Country.MAF=Saint Martin 186 | Country.MAR=Morocco 187 | Country.MCO=Monaco 188 | # Country.MDA=Moldova, Republic of 189 | Country.MDA=Moldova 190 | Country.MDG=Madagascar 191 | Country.MDV=Maldives 192 | Country.MEX=Mexico 193 | Country.MHL=Marshall Islands 194 | Country.MKD=North Macedonia 195 | Country.MLI=Mali 196 | Country.MLT=Malta 197 | Country.MMR=Myanmar 198 | Country.MNE=Montenegro 199 | Country.MNG=Mongolia 200 | Country.MNP=Northern Mariana Islands 201 | Country.MOZ=Mozambique 202 | Country.MRT=Mauritania 203 | Country.MSR=Montserrat 204 | Country.MTQ=Martinique 205 | Country.MUS=Mauritius 206 | Country.MWI=Malawi 207 | Country.MYS=Malaysia 208 | Country.MYT=Mayotte 209 | Country.NAM=Namibia 210 | Country.NCL=New Caledonia 211 | Country.NER=Niger 212 | Country.NFK=Norfolk Island 213 | Country.NGA=Nigeria 214 | Country.NIC=Nicaragua 215 | Country.NIU=Niue 216 | Country.NLD=Netherlands 217 | Country.NOR=Norway 218 | Country.NPL=Nepal 219 | Country.NRU=Nauru 220 | Country.NZL=New Zealand 221 | Country.OMN=Oman 222 | Country.PAK=Pakistan 223 | Country.PAN=Panama 224 | Country.PCN=Pitcairn 225 | Country.PER=Peru 226 | Country.PHL=Philippines 227 | Country.PLW=Palau 228 | Country.PNG=Papua New Guinea 229 | Country.POL=Poland 230 | Country.PRI=Puerto Rico 231 | # Country.PRK=Korea (Democratic People's Republic of) 232 | Country.PRK=North Korea 233 | Country.PRT=Portugal 234 | Country.PRY=Paraguay 235 | # Country.PSE=Palestine, State of 236 | Country.PSE=Palestine 237 | Country.PYF=French Polynesia 238 | Country.QAT=Qatar 239 | Country.REU=Réunion 240 | Country.ROU=Romania 241 | # Country.RUS=Russian Federation 242 | Country.RUS=Russia 243 | Country.RWA=Rwanda 244 | Country.SAU=Saudi Arabia 245 | # Country.Sco didn't exist 246 | Country.Sco=UK - Scotland 247 | Country.SDN=Sudan 248 | Country.SEN=Senegal 249 | Country.SGP=Singapore 250 | Country.SGS=South Georgia and the South Sandwich Islands 251 | Country.SHN=Saint Helena, Ascension and Tristan da Cunha 252 | Country.SJM=Svalbard and Jan Mayen 253 | Country.SLB=Solomon Islands 254 | Country.SLE=Sierra Leone 255 | Country.SLV=El Salvador 256 | Country.SMR=San Marino 257 | Country.SOM=Somalia 258 | Country.SPM=Saint Pierre and Miquelon 259 | Country.SRB=Serbia 260 | Country.SSD=South Sudan 261 | Country.STP=Sao Tome and Principe 262 | Country.SUR=Suriname 263 | Country.SVK=Slovakia 264 | Country.SVN=Slovenia 265 | Country.SWE=Sweden 266 | Country.SWZ=Eswatini 267 | Country.SYC=Seychelles 268 | # Country.SYR=Syrian Arab Republic 269 | Country.SYR=Syria 270 | Country.TCA=Turks and Caicos Islands 271 | Country.TCD=Chad 272 | Country.TGO=Togo 273 | Country.THA=Thailand 274 | Country.TJK=Tajikistan 275 | Country.TKL=Tokelau 276 | Country.TKM=Turkmenistan 277 | Country.TLS=Timor-Leste 278 | Country.TON=Tonga 279 | Country.TTO=Trinidad and Tobago 280 | Country.TUN=Tunisia 281 | Country.TUR=Turkey 282 | Country.TUV=Tuvalu 283 | # Country.TWN=Taiwan, Province of China 284 | Country.TWN=Taiwan (China) 285 | # Country.TZA=Tanzania, United Republic of 286 | Country.TZA=Tanzania 287 | Country.UGA=Uganda 288 | Country.UKR=Ukraine 289 | Country.UMI=United States Minor Outlying Islands 290 | Country.URY=Uruguay 291 | # Country.USA=United States of America: removed because it's defined as its own continent 292 | Country.UZB=Uzbekistan 293 | # Country.VAT=Holy See: removed because it's part of Rome, ITA=Italy 294 | Country.VCT=Saint Vincent and the Grenadines 295 | # Country.VEN=Venezuela (Bolivarian Republic of) 296 | Country.VEN=Venezuela 297 | # Country.VIR=Virgin Islands (U.S.) + Virgin Islands (British) 298 | Country.VIR=Virgin Islands 299 | Country.VNM=Viet Nam 300 | Country.VUT=Vanuatu 301 | Country.WLF=Wallis and Futuna 302 | Country.WSM=Samoa 303 | Country.YEM=Yemen 304 | Country.ZAF=South Africa 305 | Country.ZMB=Zambia 306 | Country.ZWE=Zimbabwe 307 | 308 | Country.AL=Alabama 309 | Country.AK=Alaska 310 | Country.AZ=Arizona 311 | Country.AR=Arkansas 312 | Country.CA=California 313 | Country.CO=Colorado 314 | Country.CT=Connecticut 315 | Country.DE=Delaware 316 | # Country.DC=District of Columbia 317 | Country.DC=D.C. 318 | Country.FL=Florida 319 | Country.GA=Georgia 320 | Country.HI=Hawaii 321 | Country.ID=Idaho 322 | Country.IL=Illinois 323 | Country.IN=Indiana 324 | Country.IA=Iowa 325 | Country.KS=Kansas 326 | Country.KY=Kentucky 327 | Country.LA=Louisiana 328 | Country.ME=Maine 329 | Country.MD=Maryland 330 | Country.MA=Massachusetts 331 | Country.MI=Michigan 332 | Country.MN=Minnesota 333 | Country.MS=Mississippi 334 | Country.MO=Missouri 335 | Country.MT=Montana 336 | Country.NE=Nebraska 337 | Country.NV=Nevada 338 | Country.NH=New Hampshire 339 | Country.NJ=New Jersey 340 | Country.NM=New Mexico 341 | Country.NY=New York 342 | Country.NC=North Carolina 343 | Country.ND=North Dakota 344 | Country.OH=Ohio 345 | Country.OK=Oklahoma 346 | Country.OR=Oregon 347 | Country.PA=Pennsylvania 348 | Country.RI=Rhode Island 349 | Country.SC=South Carolina 350 | Country.SD=South Dakota 351 | Country.TN=Tennessee 352 | Country.TX=Texas 353 | Country.UT=Utah 354 | Country.VT=Vermont 355 | Country.VA=Virginia 356 | Country.WA=Washington 357 | Country.WV=West Virginia 358 | Country.WI=Wisconsin 359 | Country.WY=Wyoming 360 | 361 | Country.ABW.Continent=SA 362 | Country.AFG.Continent=AS 363 | Country.AGO.Continent=AF 364 | Country.AIA.Continent=MA 365 | Country.ALB.Continent=EU 366 | Country.AND.Continent=EU 367 | Country.ARE.Continent=AS 368 | Country.ARG.Continent=SA 369 | Country.ARM.Continent=AS 370 | Country.ASM.Continent=OC 371 | Country.ATA.Continent=AA 372 | Country.ATF.Continent=OC 373 | Country.ATG.Continent=MA 374 | Country.AUS.Continent=OC 375 | Country.AUT.Continent=EU 376 | Country.AZE.Continent=AS 377 | Country.BDI.Continent=AF 378 | Country.BEL.Continent=EU 379 | Country.BEN.Continent=AF 380 | Country.BES.Continent=SA 381 | Country.BFA.Continent=AF 382 | Country.BGD.Continent=AS 383 | Country.BGR.Continent=EU 384 | Country.BHR.Continent=AS 385 | Country.BHS.Continent=MA 386 | Country.BIH.Continent=EU 387 | Country.BLM.Continent=MA 388 | Country.BLR.Continent=EU 389 | Country.BLZ.Continent=MA 390 | Country.BMU.Continent=OC 391 | Country.BOL.Continent=SA 392 | Country.BRA.Continent=SA 393 | Country.BRB.Continent=MA 394 | Country.BRN.Continent=AS 395 | Country.BTN.Continent=AS 396 | Country.BVT.Continent=OC 397 | Country.BWA.Continent=AF 398 | Country.CAF.Continent=AF 399 | Country.CAN.Continent=UC 400 | Country.CCK.Continent=AS 401 | Country.CHE.Continent=EU 402 | Country.CHL.Continent=SA 403 | Country.CHN.Continent=AS 404 | Country.CIV.Continent=AF 405 | Country.CMR.Continent=AF 406 | Country.COD.Continent=AF 407 | Country.COG.Continent=AF 408 | Country.COK.Continent=OC 409 | Country.COL.Continent=SA 410 | Country.COM.Continent=AF 411 | Country.CPV.Continent=AF 412 | Country.CRI.Continent=MA 413 | Country.CUB.Continent=MA 414 | Country.CUW.Continent=SA 415 | Country.CXR.Continent=AS 416 | Country.CYM.Continent=MA 417 | Country.CYP.Continent=EU 418 | Country.CZE.Continent=EU 419 | Country.DEU.Continent=EU 420 | Country.DJI.Continent=AF 421 | Country.DMA.Continent=MA 422 | Country.DNK.Continent=EU 423 | Country.DOM.Continent=MA 424 | Country.DZA.Continent=AF 425 | Country.ECU.Continent=SA 426 | Country.EGY.Continent=AF 427 | Country.ERI.Continent=AF 428 | Country.ESH.Continent=AF 429 | Country.ESP.Continent=EU 430 | Country.EST.Continent=EU 431 | Country.ETH.Continent=AF 432 | Country.FIN.Continent=EU 433 | Country.FJI.Continent=OC 434 | Country.FLK.Continent=SA 435 | Country.FRA.Continent=EU 436 | Country.FRO.Continent=EU 437 | Country.FSM.Continent=OC 438 | Country.GAB.Continent=AF 439 | Country.GBR.Continent=EU 440 | Country.GEO.Continent=AS 441 | Country.GGY.Continent=EU 442 | Country.GHA.Continent=AF 443 | Country.GIB.Continent=EU 444 | Country.GIN.Continent=AF 445 | Country.GLP.Continent=MA 446 | Country.GMB.Continent=AF 447 | Country.GNB.Continent=AF 448 | Country.GNQ.Continent=AF 449 | Country.GRC.Continent=EU 450 | Country.GRD.Continent=MA 451 | Country.GRL.Continent=AA 452 | Country.GTM.Continent=MA 453 | Country.GUF.Continent=SA 454 | Country.GUM.Continent=OC 455 | Country.GUY.Continent=SA 456 | Country.HKG.Continent=AS 457 | Country.HMD.Continent=OC 458 | Country.HND.Continent=MA 459 | Country.HRV.Continent=EU 460 | Country.HTI.Continent=MA 461 | Country.HUN.Continent=EU 462 | Country.IDN.Continent=AS 463 | Country.IND.Continent=AS 464 | Country.IOT.Continent=OC 465 | Country.IRL.Continent=EU 466 | Country.IRN.Continent=AS 467 | Country.IRQ.Continent=AS 468 | Country.ISL.Continent=EU 469 | Country.ISR.Continent=AS 470 | Country.ITA.Continent=EU 471 | Country.JAM.Continent=MA 472 | Country.JEY.Continent=EU 473 | Country.JOR.Continent=AS 474 | Country.JPN.Continent=AS 475 | Country.KAZ.Continent=AS 476 | Country.KEN.Continent=AF 477 | Country.KGZ.Continent=AS 478 | Country.KHM.Continent=AS 479 | Country.KIR.Continent=OC 480 | Country.KNA.Continent=MA 481 | Country.KOR.Continent=AS 482 | Country.KWT.Continent=AS 483 | Country.LAO.Continent=AS 484 | Country.LBN.Continent=AS 485 | Country.LBR.Continent=AF 486 | Country.LBY.Continent=AS 487 | Country.LCA.Continent=MA 488 | Country.LIE.Continent=EU 489 | Country.LKA.Continent=AS 490 | Country.LSO.Continent=AF 491 | Country.LTU.Continent=EU 492 | Country.LUX.Continent=EU 493 | Country.LVA.Continent=EU 494 | Country.MAC.Continent=AS 495 | Country.MAF.Continent=MA 496 | Country.MAR.Continent=AF 497 | Country.MCO.Continent=EU 498 | Country.MDA.Continent=EU 499 | Country.MDG.Continent=AF 500 | Country.MDV.Continent=OC 501 | Country.MEX.Continent=MA 502 | Country.MHL.Continent=OC 503 | Country.MKD.Continent=EU 504 | Country.MLI.Continent=AF 505 | Country.MLT.Continent=EU 506 | Country.MMR.Continent=AS 507 | Country.MNE.Continent=EU 508 | Country.MNG.Continent=AS 509 | Country.MNP.Continent=OC 510 | Country.MOZ.Continent=AF 511 | Country.MRT.Continent=AF 512 | Country.MSR.Continent=MA 513 | Country.MTQ.Continent=MA 514 | Country.MUS.Continent=AF 515 | Country.MWI.Continent=AF 516 | Country.MYS.Continent=AS 517 | Country.MYT.Continent=AF 518 | Country.NAM.Continent=AF 519 | Country.NCL.Continent=OC 520 | Country.NER.Continent=AF 521 | Country.NFK.Continent=OC 522 | Country.NGA.Continent=AF 523 | Country.NIC.Continent=MA 524 | Country.NIU.Continent=OC 525 | Country.NLD.Continent=EU 526 | Country.NOR.Continent=EU 527 | Country.NPL.Continent=AS 528 | Country.NRU.Continent=OC 529 | Country.NZL.Continent=OC 530 | Country.OMN.Continent=AS 531 | Country.PAK.Continent=AS 532 | Country.PAN.Continent=MA 533 | Country.PCN.Continent=OC 534 | Country.PER.Continent=SA 535 | Country.PHL.Continent=AS 536 | Country.PLW.Continent=AS 537 | Country.PNG.Continent=AS 538 | Country.POL.Continent=EU 539 | Country.PRI.Continent=MA 540 | Country.PRK.Continent=AS 541 | Country.PRT.Continent=EU 542 | Country.PRY.Continent=SA 543 | Country.PSE.Continent=AS 544 | Country.PYF.Continent=OC 545 | Country.QAT.Continent=AS 546 | Country.REU.Continent=AF 547 | Country.ROU.Continent=EU 548 | Country.RUS.Continent=AS 549 | Country.RWA.Continent=AF 550 | Country.SAU.Continent=AS 551 | Country.Sco.Continent=EU 552 | Country.SDN.Continent=AF 553 | Country.SEN.Continent=AF 554 | Country.SGP.Continent=AS 555 | Country.SGS.Continent=OC 556 | Country.SHN.Continent=OC 557 | Country.SJM.Continent=AA 558 | Country.SLB.Continent=OC 559 | Country.SLE.Continent=AF 560 | Country.SLV.Continent=MA 561 | Country.SMR.Continent=EU 562 | Country.SOM.Continent=AF 563 | Country.SPM.Continent=UC 564 | Country.SRB.Continent=EU 565 | Country.SSD.Continent=AF 566 | Country.STP.Continent=AF 567 | Country.SUR.Continent=SA 568 | Country.SVK.Continent=EU 569 | Country.SVN.Continent=EU 570 | Country.SWE.Continent=EU 571 | Country.SWZ.Continent=AF 572 | Country.SYC.Continent=OC 573 | Country.SYR.Continent=AS 574 | Country.TCA.Continent=MA 575 | Country.TCD.Continent=AF 576 | Country.TGO.Continent=AF 577 | Country.THA.Continent=AS 578 | Country.TJK.Continent=AS 579 | Country.TKL.Continent=OC 580 | Country.TKM.Continent=AS 581 | Country.TLS.Continent=AS 582 | Country.TON.Continent=OC 583 | Country.TTO.Continent=SA 584 | Country.TUN.Continent=AF 585 | Country.TUR.Continent=AS 586 | Country.TUV.Continent=OC 587 | Country.TWN.Continent=AS 588 | Country.TZA.Continent=AF 589 | Country.UGA.Continent=AF 590 | Country.UKR.Continent=EU 591 | Country.UMI.Continent=OC 592 | Country.URY.Continent=SA 593 | Country.UZB.Continent=AF 594 | Country.VCT.Continent=MA 595 | Country.VEN.Continent=SA 596 | Country.VIR.Continent=MA 597 | Country.VNM.Continent=AS 598 | Country.VUT.Continent=OC 599 | Country.WLF.Continent=OC 600 | Country.WSM.Continent=OC 601 | Country.YEM.Continent=AS 602 | Country.ZAF.Continent=AF 603 | Country.ZMB.Continent=AF 604 | Country.ZWE.Continent=AF 605 | 606 | Country.AL.Continent=UC 607 | Country.AK.Continent=UC 608 | Country.AZ.Continent=UC 609 | Country.AR.Continent=UC 610 | Country.CA.Continent=UC 611 | Country.CO.Continent=UC 612 | Country.CT.Continent=UC 613 | Country.DE.Continent=UC 614 | Country.DC.Continent=UC 615 | Country.FL.Continent=UC 616 | Country.GA.Continent=UC 617 | Country.HI.Continent=UC 618 | Country.ID.Continent=UC 619 | Country.IL.Continent=UC 620 | Country.IN.Continent=UC 621 | Country.IA.Continent=UC 622 | Country.KS.Continent=UC 623 | Country.KY.Continent=UC 624 | Country.LA.Continent=UC 625 | Country.ME.Continent=UC 626 | Country.MD.Continent=UC 627 | Country.MA.Continent=UC 628 | Country.MI.Continent=UC 629 | Country.MN.Continent=UC 630 | Country.MS.Continent=UC 631 | Country.MO.Continent=UC 632 | Country.MT.Continent=UC 633 | Country.NE.Continent=UC 634 | Country.NV.Continent=UC 635 | Country.NH.Continent=UC 636 | Country.NJ.Continent=UC 637 | Country.NM.Continent=UC 638 | Country.NY.Continent=UC 639 | Country.NC.Continent=UC 640 | Country.ND.Continent=UC 641 | Country.OH.Continent=UC 642 | Country.OK.Continent=UC 643 | Country.OR.Continent=UC 644 | Country.PA.Continent=UC 645 | Country.RI.Continent=UC 646 | Country.SC.Continent=UC 647 | Country.SD.Continent=UC 648 | Country.TN.Continent=UC 649 | Country.TX.Continent=UC 650 | Country.UT.Continent=UC 651 | Country.VT.Continent=UC 652 | Country.VA.Continent=UC 653 | Country.WA.Continent=UC 654 | Country.WV.Continent=UC 655 | Country.WI.Continent=UC 656 | Country.WY.Continent=UC --------------------------------------------------------------------------------