├── .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 |
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 |
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 |
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 |
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 |
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
--------------------------------------------------------------------------------