wörk wörk (day 4)

* added checkbox JList for preset mod list
* added multiselect of JList without pressing CTRL
* added compareable for Mod object
* added user modsets
* added functions to add/save/remove/clone Modset to user config
* added fast check (only detect byte size changes)
* added load user modsets
* added "focusing" correct button corresponding to the tab
* added JComboBox to select a preset in updater
* changed button alignment
* fixed synclist for single ModFile

#coronatime
This commit is contained in:
Niklas 2020-03-29 01:54:40 +01:00
parent 5eeffde3c3
commit 112bc228d4
15 changed files with 743 additions and 190 deletions

View File

@ -46,6 +46,11 @@
<artifactId>zsyncer</artifactId> <artifactId>zsyncer</artifactId>
<version>1de0d3f651</version> <version>1de0d3f651</version>
</dependency> </dependency>
<dependency>
<groupId>com.jgoodies</groupId>
<artifactId>jgoodies-forms</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -42,10 +42,14 @@
<properties> <properties>
<alignmentY value="0.0"/> <alignmentY value="0.0"/>
<borderPainted value="true"/> <borderPainted value="true"/>
<focusCycleRoot value="false"/>
<focusPainted value="false"/> <focusPainted value="false"/>
<focusable value="true"/> <focusable value="false"/>
<font size="16"/> <font size="16"/>
<horizontalAlignment value="2"/>
<icon value="icons/settings_16.png"/> <icon value="icons/settings_16.png"/>
<iconTextGap value="10"/>
<inheritsPopupMenu value="true"/>
<margin top="0" left="0" bottom="0" right="0"/> <margin top="0" left="0" bottom="0" right="0"/>
<text resource-bundle="lang" key="settings"/> <text resource-bundle="lang" key="settings"/>
</properties> </properties>
@ -64,8 +68,11 @@
</constraints> </constraints>
<properties> <properties>
<focusPainted value="false"/> <focusPainted value="false"/>
<focusable value="false"/>
<font size="16"/> <font size="16"/>
<horizontalAlignment value="2"/>
<icon value="icons/download_16.png"/> <icon value="icons/download_16.png"/>
<iconTextGap value="10"/>
<text resource-bundle="lang" key="update"/> <text resource-bundle="lang" key="update"/>
</properties> </properties>
</component> </component>
@ -75,8 +82,11 @@
</constraints> </constraints>
<properties> <properties>
<focusPainted value="false"/> <focusPainted value="false"/>
<focusable value="false"/>
<font size="16"/> <font size="16"/>
<horizontalAlignment value="2"/>
<icon value="icons/play_16.png"/> <icon value="icons/play_16.png"/>
<iconTextGap value="10"/>
<margin top="0" left="0" bottom="0" right="0"/> <margin top="0" left="0" bottom="0" right="0"/>
<text resource-bundle="lang" key="play"/> <text resource-bundle="lang" key="play"/>
</properties> </properties>
@ -168,8 +178,11 @@
</constraints> </constraints>
<properties> <properties>
<focusPainted value="false"/> <focusPainted value="false"/>
<focusable value="false"/>
<font size="16"/> <font size="16"/>
<horizontalAlignment value="2"/>
<icon value="icons/preset_16.png"/> <icon value="icons/preset_16.png"/>
<iconTextGap value="10"/>
<margin top="0" left="0" bottom="0" right="0"/> <margin top="0" left="0" bottom="0" right="0"/>
<text resource-bundle="lang" key="presets"/> <text resource-bundle="lang" key="presets"/>
</properties> </properties>
@ -180,8 +193,11 @@
</constraints> </constraints>
<properties> <properties>
<focusPainted value="false"/> <focusPainted value="false"/>
<focusable value="false"/>
<font size="16"/> <font size="16"/>
<horizontalAlignment value="2"/>
<icon value="icons/changelog_16.png"/> <icon value="icons/changelog_16.png"/>
<iconTextGap value="10"/>
<text resource-bundle="lang" key="changelog"/> <text resource-bundle="lang" key="changelog"/>
</properties> </properties>
</component> </component>
@ -445,20 +461,24 @@
<text resource-bundle="lang" key="abort"/> <text resource-bundle="lang" key="abort"/>
</properties> </properties>
</component> </component>
<component id="d7e4b" class="javax.swing.JButton" binding="syncCheckButton"> <component id="d7e4b" class="javax.swing.JButton" binding="syncIntensiveCheckButton">
<constraints> <constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/> <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints> </constraints>
<properties> <properties>
<enabled value="false"/> <enabled value="false"/>
<text resource-bundle="lang" key="check"/> <text resource-bundle="lang" key="intensive_check"/>
</properties> </properties>
</component> </component>
<hspacer id="a2208"> <component id="df55f" class="javax.swing.JButton" binding="syncFastCheckButton">
<constraints> <constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/> <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints> </constraints>
</hspacer> <properties>
<enabled value="false"/>
<text resource-bundle="lang" key="fast_check"/>
</properties>
</component>
</children> </children>
</grid> </grid>
<component id="c97ef" class="javax.swing.JLabel"> <component id="c97ef" class="javax.swing.JLabel">
@ -561,23 +581,25 @@
</component> </component>
</children> </children>
</grid> </grid>
<grid id="b8ac" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <grid id="b8ac" layout-manager="FormLayout">
<margin top="0" left="0" bottom="0" right="0"/> <rowspec value="center:max(d;4px):noGrow"/>
<rowspec value="top:4dlu:noGrow"/>
<rowspec value="center:max(d;4px):noGrow"/>
<rowspec value="top:4dlu:noGrow"/>
<rowspec value="center:max(d;4px):noGrow"/>
<rowspec value="top:4dlu:noGrow"/>
<rowspec value="center:max(d;4px):noGrow"/>
<colspec value="fill:d:grow"/>
<colspec value="left:4dlu:noGrow"/>
<colspec value="fill:d:grow"/>
<constraints border-constraint="Center"/> <constraints border-constraint="Center"/>
<properties/> <properties/>
<border type="none"/> <border type="none"/>
<children> <children>
<component id="e74aa" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="lang" key="total_file_size"/>
</properties>
</component>
<component id="f5997" class="javax.swing.JLabel"> <component id="f5997" class="javax.swing.JLabel">
<constraints> <constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints> </constraints>
<properties> <properties>
<text resource-bundle="lang" key="speed"/> <text resource-bundle="lang" key="speed"/>
@ -585,31 +607,17 @@
</component> </component>
<component id="25551" class="javax.swing.JLabel"> <component id="25551" class="javax.swing.JLabel">
<constraints> <constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints> </constraints>
<properties> <properties>
<text resource-bundle="lang" key="remaining_time"/> <text resource-bundle="lang" key="remaining_time"/>
</properties> </properties>
</component> </component>
<component id="b28b6" class="javax.swing.JLabel" binding="syncSizeLabel"> <component id="9148" class="javax.swing.JLabel" binding="syncFileCountLabel">
<constraints> <constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> <grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints> <forms/>
<properties>
<text value="0.0 B"/>
</properties>
</component>
<component id="617c7" class="javax.swing.JLabel" binding="syncDownloadSpeedLabel">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<component id="b64ac" class="javax.swing.JLabel">
<constraints>
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints> </constraints>
<properties> <properties>
<text value=""/> <text value=""/>
@ -617,15 +625,44 @@
</component> </component>
<component id="28be3" class="javax.swing.JLabel"> <component id="28be3" class="javax.swing.JLabel">
<constraints> <constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints> </constraints>
<properties> <properties>
<text resource-bundle="lang" key="file_count"/> <text resource-bundle="lang" key="file_count"/>
</properties> </properties>
</component> </component>
<component id="9148" class="javax.swing.JLabel" binding="syncFileCountLabel"> <component id="e74aa" class="javax.swing.JLabel">
<constraints> <constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties>
<text resource-bundle="lang" key="total_file_size"/>
</properties>
</component>
<component id="b28b6" class="javax.swing.JLabel" binding="syncSizeLabel">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties>
<text value="0.0 B/0.0 B"/>
</properties>
</component>
<component id="617c7" class="javax.swing.JLabel" binding="syncDownloadSpeedLabel">
<constraints>
<grid row="4" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
<component id="63dca" class="javax.swing.JLabel">
<constraints>
<grid row="6" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints> </constraints>
<properties> <properties>
<text value=""/> <text value=""/>
@ -793,7 +830,7 @@
<text resource-bundle="lang" key="expand_all"/> <text resource-bundle="lang" key="expand_all"/>
</properties> </properties>
</component> </component>
<component id="43543" class="javax.swing.JComboBox" binding="comboBox1" default-binding="true"> <component id="43543" class="javax.swing.JComboBox" binding="syncPresetCombo">
<constraints> <constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/> <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints> </constraints>
@ -945,10 +982,78 @@
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/> <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints> </constraints>
<properties> <properties>
<verticalScrollBarPolicy value="22"/> <horizontalScrollBarPolicy value="31"/>
<verticalScrollBarPolicy value="20"/>
</properties>
<border type="empty"/>
<children>
<grid id="87071" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="100" left="70" bottom="100" right="70"/>
<constraints/>
<properties/>
<border type="none"/>
<children>
<grid id="1679e" binding="presetNotePane" layout-manager="FormLayout">
<rowspec value="center:d:noGrow"/>
<rowspec value="top:4dlu:noGrow"/>
<rowspec value="center:max(d;4px):noGrow"/>
<rowspec value="top:4dlu:noGrow"/>
<rowspec value="center:max(d;4px):noGrow"/>
<colspec value="fill:d:grow"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<visible value="false"/>
</properties> </properties>
<border type="none"/> <border type="none"/>
<children/> <children>
<component id="7b832" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties>
<font size="14" style="1"/>
<text resource-bundle="lang" key="note"/>
</properties>
</component>
<component id="d466d" class="javax.swing.JButton" binding="presetNoteButton">
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties>
<text resource-bundle="lang" key="clone_preset"/>
</properties>
</component>
<grid id="8f1ff" binding="presetNotePaneWrapper" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<forms/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="862fc" class="javax.swing.JTextPane" binding="presetNoteTextPane">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="6" anchor="0" fill="3" indent="0" use-parent-layout="false">
<preferred-size width="200" height="50"/>
</grid>
</constraints>
<properties>
<enabled value="true"/>
<text resource-bundle="lang" key="presets_note"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
</children>
</grid>
</children>
</scrollpane> </scrollpane>
<component id="3960a" class="javax.swing.JLabel"> <component id="3960a" class="javax.swing.JLabel">
<constraints> <constraints>
@ -1033,7 +1138,9 @@
<preferred-size width="150" height="50"/> <preferred-size width="150" height="50"/>
</grid> </grid>
</constraints> </constraints>
<properties/> <properties>
<selectionMode value="1"/>
</properties>
</component> </component>
<component id="4fbf3" class="javax.swing.JList" binding="modList"> <component id="4fbf3" class="javax.swing.JList" binding="modList">
<constraints> <constraints>

View File

@ -3,10 +3,12 @@ package de.mc8051.arma3launcher;
import de.mc8051.arma3launcher.interfaces.Observer; import de.mc8051.arma3launcher.interfaces.Observer;
import de.mc8051.arma3launcher.model.JCheckBoxTree; import de.mc8051.arma3launcher.model.JCheckBoxTree;
import de.mc8051.arma3launcher.model.ModListRenderer; import de.mc8051.arma3launcher.model.ModListRenderer;
import de.mc8051.arma3launcher.model.MultiSelectModel;
import de.mc8051.arma3launcher.model.PresetListRenderer; import de.mc8051.arma3launcher.model.PresetListRenderer;
import de.mc8051.arma3launcher.model.PresetTableModel; import de.mc8051.arma3launcher.model.PresetTableModel;
import de.mc8051.arma3launcher.model.RepositoryTreeNode; import de.mc8051.arma3launcher.model.RepositoryTreeNode;
import de.mc8051.arma3launcher.model.ServerTableModel; import de.mc8051.arma3launcher.model.ServerTableModel;
import de.mc8051.arma3launcher.model.TabbedPaneUI;
import de.mc8051.arma3launcher.objects.AbstractMod; import de.mc8051.arma3launcher.objects.AbstractMod;
import de.mc8051.arma3launcher.objects.Changelog; import de.mc8051.arma3launcher.objects.Changelog;
import de.mc8051.arma3launcher.objects.Mod; import de.mc8051.arma3launcher.objects.Mod;
@ -24,10 +26,12 @@ import de.mc8051.arma3launcher.utils.Humanize;
import de.mc8051.arma3launcher.utils.ImageUtils; import de.mc8051.arma3launcher.utils.ImageUtils;
import de.mc8051.arma3launcher.utils.LangUtils; import de.mc8051.arma3launcher.utils.LangUtils;
import de.mc8051.arma3launcher.utils.TaskBarUtils; import de.mc8051.arma3launcher.utils.TaskBarUtils;
import org.json.JSONArray;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.EmptyBorder; import javax.swing.border.EmptyBorder;
import javax.swing.plaf.basic.BasicTabbedPaneUI; import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.DefaultFormatter; import javax.swing.text.DefaultFormatter;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.DefaultTreeModel;
@ -37,12 +41,12 @@ import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.ItemEvent; import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.net.URL; import java.net.URL;
@ -51,11 +55,12 @@ import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.List;
import java.util.Properties;
import java.util.Scanner; import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
/** /**
@ -125,13 +130,13 @@ public class LauncherGUI implements Observer {
private JButton expandAllButton; private JButton expandAllButton;
private JProgressBar syncCheckProgress; private JProgressBar syncCheckProgress;
private JButton syncCheckAbortButton; private JButton syncCheckAbortButton;
private JButton syncCheckButton; private JButton syncIntensiveCheckButton;
public JProgressBar syncDownloadProgress; public JProgressBar syncDownloadProgress;
public JProgressBar syncFileProgress; public JProgressBar syncFileProgress;
private JButton syncDownloadButton; private JButton syncDownloadButton;
private JButton syncDownloadAbortButton; private JButton syncDownloadAbortButton;
private JButton syncPauseButton; private JButton syncPauseButton;
private JComboBox comboBox1; private JComboBox syncPresetCombo;
private JButton refreshRepoButton; private JButton refreshRepoButton;
private JPanel updateTreePanel; private JPanel updateTreePanel;
private JScrollPane updateTreeScrolPane; private JScrollPane updateTreeScrolPane;
@ -161,6 +166,11 @@ public class LauncherGUI implements Observer {
private JLabel aboutProjectLabel; private JLabel aboutProjectLabel;
private JLabel aboutDeveloperLabel; private JLabel aboutDeveloperLabel;
private JLabel aboutCopyrightLabel; private JLabel aboutCopyrightLabel;
private JButton syncFastCheckButton;
private JButton presetNoteButton;
private JTextPane presetNoteTextPane;
private JPanel presetNotePaneWrapper;
private JPanel presetNotePane;
private JCheckBoxTree repoTree; private JCheckBoxTree repoTree;
private FileChecker fileChecker; private FileChecker fileChecker;
@ -186,7 +196,9 @@ public class LauncherGUI implements Observer {
RepositoryManger.getInstance().refreshModset(); RepositoryManger.getInstance().refreshModset();
}).start(); }).start();
updateTreePanel.remove(tree1); switchTab(Tab.PLAY);
updateTreePanel.removeAll();
repoTree = new JCheckBoxTree(); repoTree = new JCheckBoxTree();
updateTreePanel.add(repoTree, BorderLayout.CENTER); updateTreePanel.add(repoTree, BorderLayout.CENTER);
@ -197,25 +209,9 @@ public class LauncherGUI implements Observer {
updateTreePanel.revalidate(); updateTreePanel.revalidate();
updateTreePanel.repaint(); updateTreePanel.repaint();
tabbedPane1.setUI(new BasicTabbedPaneUI() { tabbedPane1.setUI(new TabbedPaneUI());
private final Insets borderInsets = new Insets(0, 0, 0, 0);
@Override Insets x = new Insets(5, 15, 5, 0);
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
}
@Override
protected Insets getContentBorderInsets(int tabPlacement) {
return borderInsets;
}
@Override
protected int calculateTabAreaHeight(int tab_placement, int run_count, int max_tab_height) {
return -5;
}
});
Insets x = new Insets(5, 5, 5, 5);
settingsPanelButton.setMargin(x); settingsPanelButton.setMargin(x);
updatePanelButton.setMargin(x); updatePanelButton.setMargin(x);
playPanelButton.setMargin(x); playPanelButton.setMargin(x);
@ -228,7 +224,9 @@ public class LauncherGUI implements Observer {
presetList.setModel(new PresetTableModel()); presetList.setModel(new PresetTableModel());
presetList.setCellRenderer(new PresetListRenderer()); presetList.setCellRenderer(new PresetListRenderer());
modList.setCellRenderer(new ModListRenderer()); modList.setCellRenderer(new ModListRenderer<String>());
modList.setSelectionModel(new MultiSelectModel());
subtitle.setText( subtitle.setText(
ArmA3Launcher.config.getString("subtitle") ArmA3Launcher.config.getString("subtitle")
@ -248,20 +246,26 @@ public class LauncherGUI implements Observer {
aboutClient.setText(ArmA3Launcher.config.getString("name") + " v" + ArmA3Launcher.VERSION); aboutClient.setText(ArmA3Launcher.config.getString("name") + " v" + ArmA3Launcher.VERSION);
aboutDeveloperLabel.setText("<html><a href=''>https://gurkengewuerz.de</a></html>"); aboutDeveloperLabel.setText("<html><a href=''>https://gurkengewuerz.de</a></html>");
aboutProjectLabel.setText("<html><a href=''>"+ArmA3Launcher.config.getString("social.github")+"</a></html>"); aboutProjectLabel.setText("<html><a href=''>" + ArmA3Launcher.config.getString("social.github") + "</a></html>");
InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("disclaimer.html"); InputStream resourceAsStream = getClass().getClassLoader().getResourceAsStream("disclaimer.html");
if(resourceAsStream != null) { if (resourceAsStream != null) {
Scanner s = new Scanner(resourceAsStream).useDelimiter("\\A"); Scanner s = new Scanner(resourceAsStream).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : ""; String result = s.hasNext() ? s.next() : "";
disclaimer.setText(result); disclaimer.setText(result);
} }
presetNoteTextPane.setHighlighter(null);
presetNoteTextPane.getCaret().setVisible(false);
presetNoteTextPane.setBackground(presetNotePaneWrapper.getBackground());
presetNoteTextPane.setCaretColor(presetNoteTextPane.getBackground());
presetNoteTextPane.setPreferredSize(new Dimension(-1, -1));
aboutCopyrightLabel.setText(aboutCopyrightLabel.getText().replace("{year}", "" + Calendar.getInstance().get(Calendar.YEAR))); aboutCopyrightLabel.setText(aboutCopyrightLabel.getText().replace("{year}", "" + Calendar.getInstance().get(Calendar.YEAR)));
twitterIcon.setBorder(new EmptyBorder(2,2,2,2)); twitterIcon.setBorder(new EmptyBorder(2, 2, 2, 2));
githubIcon.setBorder(new EmptyBorder(2,2,2,2)); githubIcon.setBorder(new EmptyBorder(2, 2, 2, 2));
settingScrollPane.getVerticalScrollBar().setUnitIncrement(16); settingScrollPane.getVerticalScrollBar().setUnitIncrement(16);
updateTreeScrolPane.getVerticalScrollBar().setUnitIncrement(16); updateTreeScrolPane.getVerticalScrollBar().setUnitIncrement(16);
@ -270,16 +274,17 @@ public class LauncherGUI implements Observer {
presetList.addListSelectionListener(e -> { presetList.addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) { if (!e.getValueIsAdjusting()) {
PresetTableModel m = (PresetTableModel) presetList.getModel(); PresetTableModel m = (PresetTableModel) presetList.getModel();
Modset modset = (Modset) m.getElementAt(presetList.getSelectedIndex()); Object elementAt = m.getElementAt(presetList.getSelectedIndex());
Modset modset = (Modset) elementAt;
if (modset.getType() == Modset.Type.SERVER) { if (modset.getType() == Modset.Type.SERVER || modset.getType() == Modset.Type.PLACEHOLDER) {
renamePresetButton.setEnabled(false); renamePresetButton.setEnabled(false);
removePresetButtom.setEnabled(false); removePresetButtom.setEnabled(false);
} else { } else {
renamePresetButton.setEnabled(true); renamePresetButton.setEnabled(true);
removePresetButtom.setEnabled(true); removePresetButtom.setEnabled(true);
} }
clonePresetButton.setEnabled(true); clonePresetButton.setEnabled(modset.getType() != Modset.Type.PLACEHOLDER);
updateModList(modset); updateModList(modset);
} }
@ -300,14 +305,11 @@ public class LauncherGUI implements Observer {
collapseAllButton.addActionListener(e -> repoTree.collapseAllNodes()); collapseAllButton.addActionListener(e -> repoTree.collapseAllNodes());
playPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(0)); playPanelButton.addActionListener(e -> switchTab(Tab.PLAY));
updatePanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(1)); updatePanelButton.addActionListener(e -> switchTab(Tab.UPDATE));
changelogButton.addActionListener(e -> { changelogButton.addActionListener(e -> switchTab(Tab.CHANGELOG));
tabbedPane1.setSelectedIndex(2); presetPanelButton.addActionListener(e -> switchTab(Tab.PRESET));
Changelog.refresh(); settingsPanelButton.addActionListener(e -> switchTab(Tab.SETTING));
});
presetPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(3));
settingsPanelButton.addActionListener(e -> tabbedPane1.setSelectedIndex(4));
refreshRepoButton.addActionListener(e -> RepositoryManger.getInstance().refreshModset()); refreshRepoButton.addActionListener(e -> RepositoryManger.getInstance().refreshModset());
expandAllButton.addActionListener(e -> repoTree.expandAllNodes()); expandAllButton.addActionListener(e -> repoTree.expandAllNodes());
@ -315,24 +317,17 @@ public class LauncherGUI implements Observer {
syncCheckAbortButton.addActionListener(e -> fileChecker.stop()); syncCheckAbortButton.addActionListener(e -> fileChecker.stop());
syncCheckButton.addActionListener(e -> { syncIntensiveCheckButton.addActionListener(e -> fileCheck(false));
syncCheckButton.setEnabled(false); syncFastCheckButton.addActionListener(e -> fileCheck(true));
syncCheckAbortButton.setEnabled(true);
syncCheckStatusLabel.setText("Running!");
new Thread(() -> fileChecker.check()).start();
refreshRepoButton.setEnabled(false);
repoTree.setCheckboxesEnabled(false);
repoTree.setCheckboxesChecked(false);
});
syncDownloadButton.addActionListener(e -> { syncDownloadButton.addActionListener(e -> {
if (!fileChecker.isChecked()) return; if (!fileChecker.isChecked()) return;
if (lastSynclist == null) return;
syncDownloadButton.setEnabled(false); syncDownloadButton.setEnabled(false);
syncDownloadAbortButton.setEnabled(true); syncDownloadAbortButton.setEnabled(true);
syncPauseButton.setEnabled(true); syncPauseButton.setEnabled(true);
syncCheckButton.setEnabled(false); syncIntensiveCheckButton.setEnabled(false);
syncFastCheckButton.setEnabled(false);
refreshRepoButton.setEnabled(false); refreshRepoButton.setEnabled(false);
new Thread(() -> syncer.sync(lastSynclist.clone())).start(); new Thread(() -> syncer.sync(lastSynclist.clone())).start();
}); });
@ -350,7 +345,7 @@ public class LauncherGUI implements Observer {
@Override @Override
public void mouseExited(MouseEvent e) { public void mouseExited(MouseEvent e) {
twitterIcon.setBorder(new EmptyBorder(2,2,2,2)); twitterIcon.setBorder(new EmptyBorder(2, 2, 2, 2));
} }
}); });
@ -362,14 +357,14 @@ public class LauncherGUI implements Observer {
@Override @Override
public void mouseExited(MouseEvent e) { public void mouseExited(MouseEvent e) {
githubIcon.setBorder(new EmptyBorder(2,2,2,2)); githubIcon.setBorder(new EmptyBorder(2, 2, 2, 2));
} }
}); });
aboutLabel.addMouseListener(new MouseAdapter() { aboutLabel.addMouseListener(new MouseAdapter() {
@Override @Override
public void mouseClicked(MouseEvent e) { public void mouseClicked(MouseEvent e) {
tabbedPane1.setSelectedIndex(5); switchTab(Tab.ABOUT);
} }
}); });
@ -400,6 +395,125 @@ public class LauncherGUI implements Observer {
openURL(ArmA3Launcher.config.getString("social.github")); openURL(ArmA3Launcher.config.getString("social.github"));
} }
}); });
modList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (presetList.getSelectedIndex() == -1) return;
JList<?> list = (JList<?>) e.getSource();
ListModel<?> model = list.getModel();
ListSelectionModel listSelectionModel = list.getSelectionModel();
int minSelectionIndex = listSelectionModel.getMinSelectionIndex();
int maxSelectionIndex = listSelectionModel.getMaxSelectionIndex();
List<String> selectedMods = new ArrayList<>();
for (int i = minSelectionIndex; i <= maxSelectionIndex; i++) {
if (listSelectionModel.isSelectedIndex(i)) {
selectedMods.add(String.valueOf(model.getElementAt(i)));
}
}
PresetTableModel model1 = (PresetTableModel) presetList.getModel();
if (presetList.getSelectedIndex() == -1) return;
Object elementAt = model1.getElementAt(presetList.getSelectedIndex());
Modset selectedModset = (Modset) elementAt;
if (selectedModset.getType() == Modset.Type.PLACEHOLDER) return;
selectedModset.getMods().clear();
selectedModset.setMods(selectedMods);
updateModsetList();
selectedModset.save();
}
});
newPresetButtom.addActionListener(e -> {
String modname = JOptionPane.showInputDialog(null, "", LangUtils.getInstance().getString("new_modset_name"));
if (modname.isEmpty()) return;
if (Modset.MODSET_LIST.containsKey(modname)) {
infoBox(LangUtils.getInstance().getString("modset_exists_msg"), LangUtils.getInstance().getString("modset_exists"));
return;
}
Modset ms = new Modset(modname, new JSONArray(), Modset.Type.CLIENT);
updateModsetList();
ms.save();
});
presetNoteButton.addActionListener(e -> clonePresetButton.doClick());
clonePresetButton.addActionListener(e -> {
if (presetList.getSelectedIndex() == -1) return;
String newName = JOptionPane.showInputDialog(null, "", LangUtils.getInstance().getString("new_modset_name"));
if (newName.isEmpty()) return;
if (Modset.MODSET_LIST.containsKey(newName)) {
infoBox(LangUtils.getInstance().getString("modset_exists_msg"), LangUtils.getInstance().getString("modset_exists"));
return;
}
PresetTableModel model1 = (PresetTableModel) presetList.getModel();
Modset selectedModset = ((Modset) model1.getElementAt(presetList.getSelectedIndex()));
Modset newModset = selectedModset.clone(newName, Modset.Type.CLIENT);
updateModsetList();
newModset.save();
});
removePresetButtom.addActionListener(e -> {
if (presetList.getSelectedIndex() == -1) return;
modList.setModel(new DefaultListModel<>());
PresetTableModel model1 = (PresetTableModel) presetList.getModel();
((Modset) model1.getElementAt(presetList.getSelectedIndex())).removeFromConfig();
updateModsetList();
});
renamePresetButton.addActionListener(e -> {
if (presetList.getSelectedIndex() == -1) return;
PresetTableModel model1 = (PresetTableModel) presetList.getModel();
Modset selectedModset = ((Modset) model1.getElementAt(presetList.getSelectedIndex()));
Object newNameO = JOptionPane.showInputDialog(null, "",
LangUtils.getInstance().getString("new_modset_name"), JOptionPane.QUESTION_MESSAGE, null, null, selectedModset.getName());
if(newNameO == null) return;
String newName = (String) newNameO;
if (newName.isEmpty()) return;
if (Modset.MODSET_LIST.containsKey(newName)) {
infoBox(LangUtils.getInstance().getString("modset_exists_msg"), LangUtils.getInstance().getString("modset_exists"));
return;
}
Modset newModset = selectedModset.clone(newName, Modset.Type.CLIENT);
updateModsetList();
selectedModset.removeFromConfig();
newModset.save();
});
syncPresetCombo.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
DefaultComboBoxModel<Modset> model = (DefaultComboBoxModel<Modset>) syncPresetCombo.getModel();
Modset elementAt = model.getElementAt(((JComboBox) e.getItemSelectable()).getSelectedIndex());
repoTree.setCheckboxesChecked(false);
if(elementAt.getType() == Modset.Type.PLACEHOLDER) return;
List<String> collect = elementAt.getMods().stream().map(Mod::getName).collect(Collectors.toList());
DefaultTreeModel repoModel = (DefaultTreeModel) repoTree.getModel();
RepositoryTreeNode root = (RepositoryTreeNode) repoModel.getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
TreeNode childAt = root.getChildAt(i);
if(!collect.contains(childAt.toString())) continue;
final TreePath treePath = new TreePath(new TreeNode[]{root, childAt});
repoTree.checkSubTree(treePath, true);
repoTree.updatePredecessorsWithCheckMode(treePath, true);
}
repoTree.revalidate();
repoTree.repaint();
updateDownloadLabel();
}
}
});
} }
public static void infoBox(String infoMessage, String titleBar) { public static void infoBox(String infoMessage, String titleBar) {
@ -438,13 +552,15 @@ public class LauncherGUI implements Observer {
if (SteamTimer.arma_running) { if (SteamTimer.arma_running) {
playButton.setEnabled(false); playButton.setEnabled(false);
playPresetButton.setEnabled(false); playPresetButton.setEnabled(false);
syncCheckButton.setEnabled(false); syncIntensiveCheckButton.setEnabled(false);
syncFastCheckButton.setEnabled(false);
refreshRepoButton.setEnabled(false); refreshRepoButton.setEnabled(false);
syncDownloadButton.setEnabled(false); syncDownloadButton.setEnabled(false);
playButton.setToolTipText(LangUtils.getInstance().getString("arma_running")); playButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
playPresetButton.setToolTipText(LangUtils.getInstance().getString("arma_running")); playPresetButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
syncCheckButton.setToolTipText(LangUtils.getInstance().getString("arma_running")); syncIntensiveCheckButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
syncFastCheckButton.setToolTipText(LangUtils.getInstance().getString("arma_running"));
} else { } else {
if (SteamTimer.steam_running) { if (SteamTimer.steam_running) {
if (pathSet) { if (pathSet) {
@ -469,20 +585,23 @@ public class LauncherGUI implements Observer {
} }
if (pathSet) { if (pathSet) {
syncCheckButton.setEnabled(true); syncIntensiveCheckButton.setEnabled(true);
syncFastCheckButton.setEnabled(true);
refreshRepoButton.setEnabled(true); refreshRepoButton.setEnabled(true);
syncDownloadButton.setEnabled(fileChecker.isChecked()); syncDownloadButton.setEnabled(fileChecker.isChecked());
syncCheckButton.setToolTipText(null); syncIntensiveCheckButton.setToolTipText(null);
refreshRepoButton.setToolTipText(null); refreshRepoButton.setToolTipText(null);
} else { } else {
syncCheckButton.setEnabled(false); syncIntensiveCheckButton.setEnabled(false);
syncFastCheckButton.setEnabled(false);
refreshRepoButton.setEnabled(false); refreshRepoButton.setEnabled(false);
syncDownloadButton.setEnabled(false); syncDownloadButton.setEnabled(false);
syncCheckButton.setToolTipText(LangUtils.getInstance().getString("path_not_set")); syncIntensiveCheckButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
syncFastCheckButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
refreshRepoButton.setToolTipText(LangUtils.getInstance().getString("path_not_set")); refreshRepoButton.setToolTipText(LangUtils.getInstance().getString("path_not_set"));
} }
} }
@ -664,7 +783,7 @@ public class LauncherGUI implements Observer {
if (!isSelected) continue; if (!isSelected) continue;
ArrayList<String> treePathList = new ArrayList<>(); ArrayList<String> treePathList = new ArrayList<>();
for (int i = 2; i < path.length; i++) { for (int i = (path.length > 2 ? 2 : 1); i < path.length; i++) {
treePathList.add(String.valueOf(((DefaultMutableTreeNode) path[i]).getUserObject())); treePathList.add(String.valueOf(((DefaultMutableTreeNode) path[i]).getUserObject()));
} }
String treePath = String.join("/", treePathList); String treePath = String.join("/", treePathList);
@ -693,14 +812,33 @@ public class LauncherGUI implements Observer {
return synclist; return synclist;
} }
public void updateModList(Modset modset) { public void updateModList(final Modset modset) {
ListModel<String> model = (ListModel) modList.getModel(); if (modset == null) return;
// TODO: Show All Mods (keyname) DefaultListModel<String> listModel = new DefaultListModel<>();
// Show not installed Mods with red font
// Select Mod if in modset.Mods if (modset.getType() == Modset.Type.PLACEHOLDER) return;
// Custom Checkbox Render int[] select = new int[modset.getMods().size()];
// Wenn modset.type == Server alle Checkboxen deaktivieren!
// Show hint that server modsets cant be edited AtomicInteger selectCounter = new AtomicInteger(0);
RepositoryManger.MOD_LIST.stream()
.filter((am) -> am instanceof Mod)
.sorted()
.forEach((abstractMod) -> {
final int i = listModel.getSize();
listModel.add(i, abstractMod.getName());
for (Mod mod : modset.getMods()) {
if (mod.getName().equals(abstractMod.getName())) {
select[selectCounter.getAndIncrement()] = i;
break;
}
}
});
modList.setModel(listModel);
modList.setSelectedIndices(select);
modList.setEnabled(modset.getType() != Modset.Type.SERVER);
presetNotePane.setVisible(modset.getType() == Modset.Type.SERVER);
modList.revalidate();
} }
public void updateRepoTree() { public void updateRepoTree() {
@ -769,6 +907,17 @@ public class LauncherGUI implements Observer {
repoTree.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() { repoTree.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() {
@Override @Override
public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) { public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) {
syncPresetCombo.setSelectedIndex(0);
updateDownloadLabel();
}
});
expandAllButton.setEnabled(true);
collapseAllButton.setEnabled(true);
}
public void updateDownloadLabel() {
lastSynclist = getSyncList(); lastSynclist = getSyncList();
if (lastSynclist.getSize() != 0) if (lastSynclist.getSize() != 0)
syncSizeLabel.setText("0.0 B/" + Humanize.binaryPrefix(lastSynclist.getSize())); syncSizeLabel.setText("0.0 B/" + Humanize.binaryPrefix(lastSynclist.getSize()));
@ -781,11 +930,6 @@ public class LauncherGUI implements Observer {
syncFileCountLabel.setText(""); syncFileCountLabel.setText("");
} }
} }
});
expandAllButton.setEnabled(true);
collapseAllButton.setEnabled(true);
}
public Color getNodeColor(String mod, ModFile mf) { public Color getNodeColor(String mod, ModFile mf) {
if (fileChecker.getAdded().containsKey(mod)) { if (fileChecker.getAdded().containsKey(mod)) {
@ -863,7 +1007,7 @@ public class LauncherGUI implements Observer {
@Override @Override
public void update(String s) { public void update(String s) {
System.out.println(s); Logger.getLogger(getClass().getName()).log(Level.INFO, "Observer received: " + s);
if (s.equals(RepositoryManger.Type.METADATA.toString())) { if (s.equals(RepositoryManger.Type.METADATA.toString())) {
switch (RepositoryManger.getInstance().getStatus(RepositoryManger.Type.METADATA)) { switch (RepositoryManger.getInstance().getStatus(RepositoryManger.Type.METADATA)) {
case ERROR: case ERROR:
@ -878,16 +1022,7 @@ public class LauncherGUI implements Observer {
Server.SERVER_LIST.forEach((name, server) -> model.add(server)); Server.SERVER_LIST.forEach((name, server) -> model.add(server));
}); });
SwingUtilities.invokeLater(() -> { updateModsetList();
PresetTableModel model = (PresetTableModel) presetList.getModel();
model.clear();
model.add(new Modset("--Server", Modset.Type.CLIENT, null, false));
Modset.MODSET_LIST.forEach((name, set) -> {
model.add(set);
});
});
break; break;
} }
} else if (s.equals("steamtimer")) { } else if (s.equals("steamtimer")) {
@ -918,7 +1053,8 @@ public class LauncherGUI implements Observer {
}); });
} }
} else if (s.equals("fileChecker")) { } else if (s.equals("fileChecker")) {
syncCheckButton.setEnabled(true); syncIntensiveCheckButton.setEnabled(true);
syncFastCheckButton.setEnabled(true);
syncCheckAbortButton.setEnabled(false); syncCheckAbortButton.setEnabled(false);
syncCheckStatusLabel.setText("Finished!"); syncCheckStatusLabel.setText("Finished!");
updateRepoTree(); updateRepoTree();
@ -933,9 +1069,14 @@ public class LauncherGUI implements Observer {
syncDownloadButton.setEnabled(true); syncDownloadButton.setEnabled(true);
syncPauseButton.setEnabled(false); syncPauseButton.setEnabled(false);
refreshRepoButton.setEnabled(true);
syncChangedFileSizeLabel.setText(Humanize.binaryPrefix(fileChecker.getSize())); syncChangedFileSizeLabel.setText(Humanize.binaryPrefix(fileChecker.getSize()));
lastSynclist = null;
} else if (s.equals("fileCheckerStopped")) { } else if (s.equals("fileCheckerStopped")) {
syncCheckButton.setEnabled(true); syncIntensiveCheckButton.setEnabled(true);
syncFastCheckButton.setEnabled(true);
syncCheckAbortButton.setEnabled(false); syncCheckAbortButton.setEnabled(false);
syncCheckProgress.setValue(0); syncCheckProgress.setValue(0);
syncCheckStatusLabel.setText("Failed!"); syncCheckStatusLabel.setText("Failed!");
@ -954,8 +1095,9 @@ public class LauncherGUI implements Observer {
syncChangedFileSizeLabel.setText("0.0 B"); syncChangedFileSizeLabel.setText("0.0 B");
lastSynclist = null;
} else if (s.equals("syncStopped")) { } else if (s.equals("syncStopped")) {
new Thread(() -> fileChecker.check()).start(); new Thread(() -> fileChecker.check(true)).start();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
syncDownloadButton.setEnabled(false); syncDownloadButton.setEnabled(false);
syncDownloadAbortButton.setEnabled(false); syncDownloadAbortButton.setEnabled(false);
@ -967,7 +1109,7 @@ public class LauncherGUI implements Observer {
TaskBarUtils.getInstance().off(); TaskBarUtils.getInstance().off();
}); });
} else if (s.equals("syncComplete")) { } else if (s.equals("syncComplete")) {
new Thread(() -> fileChecker.check()).start(); new Thread(() -> fileChecker.check(true)).start();
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
syncDownloadButton.setEnabled(false); syncDownloadButton.setEnabled(false);
syncDownloadAbortButton.setEnabled(false); syncDownloadAbortButton.setEnabled(false);
@ -1001,6 +1143,41 @@ public class LauncherGUI implements Observer {
} }
} }
private void updateModsetList() {
SwingUtilities.invokeLater(() -> {
if (((DefaultComboBoxModel<Modset>)syncPresetCombo.getModel()).getSize() > 0){
syncPresetCombo.setSelectedIndex(0);
}
PresetTableModel model = (PresetTableModel) presetList.getModel();
model.clear();
model.add(new Modset("--Server", Modset.Type.PLACEHOLDER, null, false));
Modset.MODSET_LIST.values().stream().filter((ms) -> ms.getType() == Modset.Type.SERVER).sorted().forEach(model::add);
model.add(new Modset("--User", Modset.Type.PLACEHOLDER, null, false));
Modset.MODSET_LIST.values().stream().filter((ms) -> ms.getType() == Modset.Type.CLIENT).sorted().forEach(model::add);
DefaultComboBoxModel<Modset> presetModel = new DefaultComboBoxModel<>();
presetModel.addElement(new Modset("", Modset.Type.PLACEHOLDER, null, false));
Modset.MODSET_LIST.values().stream().filter((ms) -> ms.getType() != Modset.Type.PLACEHOLDER).sorted().forEach(presetModel::addElement);
syncPresetCombo.setModel(presetModel);
});
}
public void fileCheck(boolean fastscan) {
syncIntensiveCheckButton.setEnabled(false);
syncFastCheckButton.setEnabled(false);
syncCheckAbortButton.setEnabled(true);
syncCheckStatusLabel.setText("Running!");
new Thread(() -> fileChecker.check(fastscan)).start();
refreshRepoButton.setEnabled(false);
repoTree.setCheckboxesEnabled(false);
repoTree.setCheckboxesChecked(false);
}
public void exit() { public void exit() {
fileChecker.stop(); fileChecker.stop();
syncer.stop(); syncer.stop();
@ -1012,4 +1189,59 @@ public class LauncherGUI implements Observer {
} catch (Exception ignored) { } catch (Exception ignored) {
} }
} }
public void switchTab(Tab tab) {
Color focusBackgroundColor = UIManager.getColor("Button.default.focusColor");
Color backgroundColor = UIManager.getColor("Button.background");
playPanelButton.setBackground(backgroundColor);
updatePanelButton.setBackground(backgroundColor);
changelogButton.setBackground(backgroundColor);
presetPanelButton.setBackground(backgroundColor);
settingsPanelButton.setBackground(backgroundColor);
switch (tab) {
case PLAY:
playPanelButton.setBackground(focusBackgroundColor);
break;
case UPDATE:
updatePanelButton.setBackground(focusBackgroundColor);
break;
case CHANGELOG:
changelogButton.setBackground(focusBackgroundColor);
Changelog.refresh();
break;
case PRESET:
presetPanelButton.setBackground(focusBackgroundColor);
break;
case SETTING:
settingsPanelButton.setBackground(focusBackgroundColor);
break;
}
tabbedPane1.setSelectedIndex(tab.getIndex());
}
private enum Tab {
PLAY(0),
UPDATE(1),
CHANGELOG(2),
PRESET(3),
SETTING(4),
ABOUT(5);
private int index;
Tab(int index) {
this.index = index;
}
public int getIndex() {
return index;
}
}
} }

View File

@ -233,7 +233,7 @@ public class JCheckBoxTree extends JTree {
} }
// When a node is checked/unchecked, updating the states of the predecessors // When a node is checked/unchecked, updating the states of the predecessors
protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) { public void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
TreePath parentPath = tp.getParentPath(); TreePath parentPath = tp.getParentPath();
// If it is the root, stop the recursive calls and return // If it is the root, stop the recursive calls and return
if (parentPath == null) { if (parentPath == null) {
@ -266,7 +266,7 @@ public class JCheckBoxTree extends JTree {
} }
// Recursively checks/unchecks a subtree // Recursively checks/unchecks a subtree
protected void checkSubTree(TreePath tp, boolean check) { public void checkSubTree(TreePath tp, boolean check) {
CheckedNode cn = nodesCheckingState.get(tp); CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check; cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent(); DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();

View File

@ -4,22 +4,28 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
/** /**
* Created by gurkengewuerz.de on 25.03.2020. * Created by gurkengewuerz.de on 28.03.2020.
*/ */
public class ModListRenderer extends JCheckBox implements ListCellRenderer { public class ModListRenderer<E> extends JCheckBox implements
ListCellRenderer<E> {
public Component getListCellRendererComponent(JList list, Object value, int index, private static final long serialVersionUID = 3734536442230283966L;
boolean isSelected, boolean cellHasFocus) {
@Override
public Component getListCellRendererComponent(JList<? extends E> list,
E value, int index, boolean isSelected, boolean cellHasFocus) {
setComponentOrientation(list.getComponentOrientation()); setComponentOrientation(list.getComponentOrientation());
setFont(list.getFont()); setFont(list.getFont());
setText(String.valueOf(value));
setBackground(list.getBackground()); setBackground(list.getBackground());
setForeground(list.getForeground()); setForeground(list.getForeground());
setSelected(isSelected); setSelected(isSelected);
setEnabled(list.isEnabled()); setEnabled(list.isEnabled());
setText(value == null ? "" : value.toString());
return this; return this;
} }
} }

View File

@ -0,0 +1,35 @@
package de.mc8051.arma3launcher.model;
import javax.swing.*;
/**
* Created by gurkengewuerz.de on 28.03.2020.
*/
public class MultiSelectModel extends DefaultListSelectionModel {
private int i0 = -1;
private int i1 = -1;
public void setSelectionInterval(int index0, int index1) {
if (i0 == index0 && i1 == index1) {
if (getValueIsAdjusting()) {
setValueIsAdjusting(false);
setSelection(index0, index1);
}
} else {
i0 = index0;
i1 = index1;
setValueIsAdjusting(false);
setSelection(index0, index1);
}
}
private void setSelection(int index0, int index1) {
if (super.isSelectedIndex(index0)) {
super.removeSelectionInterval(index0, index1);
} else {
super.addSelectionInterval(index0, index1);
}
}
}

View File

@ -0,0 +1,26 @@
package de.mc8051.arma3launcher.model;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.*;
/**
* Created by gurkengewuerz.de on 28.03.2020.
*/
public class TabbedPaneUI extends BasicTabbedPaneUI {
private final Insets borderInsets = new Insets(0, 0, 0, 0);
@Override
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
}
@Override
protected Insets getContentBorderInsets(int tabPlacement) {
return borderInsets;
}
@Override
protected int calculateTabAreaHeight(int tab_placement, int run_count, int max_tab_height) {
return -5;
}
}

View File

@ -5,7 +5,7 @@ import java.util.ArrayList;
/** /**
* Created by gurkengewuerz.de on 25.03.2020. * Created by gurkengewuerz.de on 25.03.2020.
*/ */
public class Mod implements AbstractMod { public class Mod implements AbstractMod, Comparable {
private String name; private String name;
private long size; private long size;
@ -37,4 +37,9 @@ public class Mod implements AbstractMod {
public Mod clone() { public Mod clone() {
return new Mod(name, size, new ArrayList<>(files)); return new Mod(name, size, new ArrayList<>(files));
} }
@Override
public int compareTo(Object o) {
return getName().compareToIgnoreCase(((Mod) o).getName());
}
} }

View File

@ -1,16 +1,22 @@
package de.mc8051.arma3launcher.objects; package de.mc8051.arma3launcher.objects;
import de.mc8051.arma3launcher.ArmA3Launcher;
import org.ini4j.Ini;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
/** /**
* Created by gurkengewuerz.de on 25.03.2020. * Created by gurkengewuerz.de on 25.03.2020.
*/ */
public class Modset { public class Modset implements Comparable {
public static HashMap<String, Modset> MODSET_LIST = new HashMap<>(); public static HashMap<String, Modset> MODSET_LIST = new HashMap<>();
@ -27,23 +33,69 @@ public class Modset {
this.type = type; this.type = type;
this.mods = mods; this.mods = mods;
if(add) MODSET_LIST.put(name, this); if (add) MODSET_LIST.put(name, this);
} }
public Modset(JSONObject o, Type type) { public Modset(String name, JSONArray modlist, Type type) {
if(!o.has("name") || !o.has("mods")) return; for (int j = 0; j < modlist.length(); j++) {
name = o.getString("name");
JSONArray modlist = o.getJSONArray("mods");
for(int j = 0; j < modlist.length(); j++){
mods.add(new Mod(modlist.getString(j))); mods.add(new Mod(modlist.getString(j)));
} }
this.type = type; this.type = type;
this.name = name;
MODSET_LIST.put(name, this); MODSET_LIST.put(name, this);
} }
public Modset(JSONObject o, Type type) {
this(o.getString("name"), o.getJSONArray("mods"), type);
}
public void save() {
if (type != Type.CLIENT) return;
Ini.Section section = ArmA3Launcher.user_config.get("presets");
if (section == null) {
section = ArmA3Launcher.user_config.add("presets");
}
if (section != null) {
List<String> list = mods.stream()
.map(Mod::getName)
.collect(Collectors.toList());
JSONArray ja = new JSONArray(list);
if (section.containsKey(name))
section.replace(name, ja.toString());
else
section.add(name, ja.toString());
try {
ArmA3Launcher.user_config.store();
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
}
}
}
public void removeFromConfig() {
MODSET_LIST.remove(name);
if (type != Type.CLIENT) return;
Ini.Section section = ArmA3Launcher.user_config.get("presets");
if (section != null) {
if (section.containsKey(name)) {
section.remove(name);
try {
ArmA3Launcher.user_config.store();
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, null, e);
}
}
}
}
public String getName() { public String getName() {
return name; return name;
} }
@ -60,8 +112,31 @@ public class Modset {
// TODO: Implement play with this Modset // TODO: Implement play with this Modset
} }
public Modset clone(String newName, Type newType) {
return new Modset(newName, newType, new ArrayList<>(mods));
}
public Modset clone() {
return new Modset(name, type, mods, false);
}
public void setMods(List<String> selectedMods) {
mods.addAll(selectedMods.stream().map(Mod::new).collect(Collectors.toList()));
}
@Override
public int compareTo(Object o) {
return getName().compareToIgnoreCase(((Modset) o).getName());
}
public static enum Type { public static enum Type {
SERVER, SERVER,
CLIENT CLIENT,
PLACEHOLDER;
}
@Override
public String toString() {
return getName();
} }
} }

View File

@ -8,9 +8,7 @@ import de.mc8051.arma3launcher.objects.Mod;
import de.mc8051.arma3launcher.objects.ModFile; import de.mc8051.arma3launcher.objects.ModFile;
import javax.swing.*; import javax.swing.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -45,6 +43,10 @@ public class FileChecker implements Observable {
} }
public void check() { public void check() {
check(false);
}
public void check(boolean fastscan) {
deleted.clear(); deleted.clear();
changed.clear(); changed.clear();
changedCount = 0; changedCount = 0;
@ -59,16 +61,16 @@ public class FileChecker implements Observable {
}); });
for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) { for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
if(stop) { if (stop) {
stop = false; stop = false;
notifyObservers("fileCheckerStopped"); notifyObservers("fileCheckerStopped");
return; return;
} }
if(abstractMod instanceof Mod) { if (abstractMod instanceof Mod) {
Mod m = (Mod) abstractMod; Mod m = (Mod) abstractMod;
for (ModFile mf : m.getFiles()) { for (ModFile mf : m.getFiles()) {
checkFile(m.getName(), mf); checkFile(m.getName(), mf, fastscan);
i++; i++;
int finalI = i; int finalI = i;
@ -76,7 +78,7 @@ public class FileChecker implements Observable {
pb.setValue(finalI); pb.setValue(finalI);
}); });
if(stop) { if (stop) {
stop = false; stop = false;
notifyObservers("fileCheckerStopped"); notifyObservers("fileCheckerStopped");
return; return;
@ -84,7 +86,7 @@ public class FileChecker implements Observable {
} }
} else if (abstractMod instanceof ModFile) { } else if (abstractMod instanceof ModFile) {
ModFile mf = (ModFile) abstractMod; ModFile mf = (ModFile) abstractMod;
checkFile(mf.getName(), mf); checkFile(mf.getName(), mf, fastscan);
i++; i++;
int finalI1 = i; int finalI1 = i;
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
@ -106,11 +108,11 @@ public class FileChecker implements Observable {
stop = true; stop = true;
} }
private void checkFile(String mod, ModFile mf) { private void checkFile(String mod, ModFile mf, boolean fastscan) {
ArrayList<ModFile> temp = new ArrayList<>(); ArrayList<ModFile> temp = new ArrayList<>();
if(!mf.exists()) { if (!mf.exists()) {
if(added.containsKey(mod)) temp =added.get(mod); if (added.containsKey(mod)) temp = added.get(mod);
temp.add(mf); temp.add(mf);
added.put(mod, temp); added.put(mod, temp);
addedCount++; addedCount++;
@ -118,8 +120,10 @@ public class FileChecker implements Observable {
return; return;
} }
if(mf.getLocalSize() != mf.getSize() || !mf.getSHA1Sum().equalsIgnoreCase(mf.getLocalGeneratedSHA1Sum())) {
if(changed.containsKey(mod)) temp =changed.get(mod); if (fastscan || !mf.getSHA1Sum().equalsIgnoreCase(mf.getLocalGeneratedSHA1Sum())) {
if (mf.getLocalSize() != mf.getSize()) {
if (changed.containsKey(mod)) temp = changed.get(mod);
temp.add(mf); temp.add(mf);
changed.put(mod, temp); changed.put(mod, temp);
changedCount++; changedCount++;
@ -127,10 +131,11 @@ public class FileChecker implements Observable {
return; return;
} }
} }
}
private void checkDeleted() { private void checkDeleted() {
String modPath = ArmA3Launcher.user_config.get("client", "modPath"); String modPath = ArmA3Launcher.user_config.get("client", "modPath");
if(modPath == null) modPath = ""; if (modPath == null) modPath = "";
try { try {
List<Path> filePathList = Files.find(Paths.get(modPath), List<Path> filePathList = Files.find(Paths.get(modPath),
@ -144,7 +149,7 @@ public class FileChecker implements Observable {
outerloop: outerloop:
for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) { for (AbstractMod abstractMod : RepositoryManger.MOD_LIST) {
if(abstractMod instanceof Mod) { if (abstractMod instanceof Mod) {
Mod m = (Mod) abstractMod; Mod m = (Mod) abstractMod;
for (ModFile mf : m.getFiles()) { for (ModFile mf : m.getFiles()) {

View File

@ -10,6 +10,7 @@ import de.mc8051.arma3launcher.objects.ModFile;
import de.mc8051.arma3launcher.objects.Modset; import de.mc8051.arma3launcher.objects.Modset;
import de.mc8051.arma3launcher.objects.Server; import de.mc8051.arma3launcher.objects.Server;
import de.mc8051.arma3launcher.utils.Callback; import de.mc8051.arma3launcher.utils.Callback;
import org.ini4j.Ini;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -27,6 +28,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.stream.Collectors;
import static java.time.temporal.ChronoUnit.SECONDS; import static java.time.temporal.ChronoUnit.SECONDS;
@ -93,10 +95,19 @@ public class RepositoryManger implements Observable {
} }
try { try {
Modset.MODSET_LIST.clear();
Ini.Section section = ArmA3Launcher.user_config.get("presets");
if (section != null) {
for (String key : section.keySet()) {
String jsonString = section.get(key);
JSONArray ja = new JSONArray(jsonString);
new Modset(key, ja, Modset.Type.CLIENT);
}
}
JSONObject jsonObject = new JSONObject(r.getBody()); JSONObject jsonObject = new JSONObject(r.getBody());
if (jsonObject.has("modsets")) { if (jsonObject.has("modsets")) {
Modset.MODSET_LIST.clear();
JSONArray modsets = jsonObject.getJSONArray("modsets"); JSONArray modsets = jsonObject.getJSONArray("modsets");
if (modsets.length() > 0) { if (modsets.length() > 0) {
for (int i = 0; i < modsets.length(); i++) { for (int i = 0; i < modsets.length(); i++) {

View File

@ -1,6 +1,6 @@
{ {
"name": "TheTown Client", "name": "TheTown Client",
"title": "Welcome! :P", "title": "Willkommen!",
"subtitle": "${name} v${version}", "subtitle": "${name} v${version}",
"sync": { "sync": {
"useragent": "TheTownSyncer", "useragent": "TheTownSyncer",

View File

@ -29,7 +29,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
<br/> <br/>
<br/> <br/>
<h1>Licenses</h1> <h1>Licenses</h1>
<h3>de.mc8051.arma3launcher</h3> <h2>de.mc8051.arma3launcher</h2>
<tt> <tt>
MIT License<br/> MIT License<br/>
<br/> <br/>
@ -54,7 +54,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
SOFTWARE.<br/> SOFTWARE.<br/>
</tt> </tt>
<br/> <br/>
<h3>com.formdev.flatlaf</h3> <h2>com.formdev.flatlaf</h2>
<a href="">https://github.com/JFormDesigner/FlatLaf</a><br/> <a href="">https://github.com/JFormDesigner/FlatLaf</a><br/>
<tt> <tt>
Copyright 2019 FormDev Software GmbH<br/> Copyright 2019 FormDev Software GmbH<br/>
@ -72,7 +72,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
limitations under the License.<br/> limitations under the License.<br/>
</tt> </tt>
<br/> <br/>
<h3>co.bitshfted.xapps.zsync</h3> <h2>co.bitshfted.xapps.zsync</h2>
<a href="">https://github.com/bitshifted/zsyncer</a><br/> <a href="">https://github.com/bitshifted/zsyncer</a><br/>
<tt> <tt>
Copyright (c) 2015, Salesforce.com, Inc. All rights reserved.<br/> Copyright (c) 2015, Salesforce.com, Inc. All rights reserved.<br/>
@ -99,7 +99,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br/> OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br/>
</tt> </tt>
<br/> <br/>
<h3>com.github.RalleYTN.SimpleRegistry</h3> <h2>com.github.RalleYTN.SimpleRegistry</h2>
<a href="">https://github.com/RalleYTN/SimpleRegistry</a><br/> <a href="">https://github.com/RalleYTN/SimpleRegistry</a><br/>
<tt> <tt>
MIT License<br/> MIT License<br/>
@ -125,7 +125,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
SOFTWARE.<br/> SOFTWARE.<br/>
</tt> </tt>
<br/> <br/>
<h3>com.typesafe.config</h3> <h2>com.typesafe.config</h2>
<a href="">https://github.com/lightbend/config</a><br/> <a href="">https://github.com/lightbend/config</a><br/>
<tt> <tt>
Copyright (C) 2011-2012 Typesafe Inc. http://typesafe.com<br/> Copyright (C) 2011-2012 Typesafe Inc. http://typesafe.com<br/>
@ -143,7 +143,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
limitations under the License.<br/> limitations under the License.<br/>
</tt> </tt>
<br/> <br/>
<h3>org.ini4j.ini4j</h3> <h2>org.ini4j.ini4j</h2>
<a href="">https://github.com/facebookarchive/ini4j</a><br/> <a href="">https://github.com/facebookarchive/ini4j</a><br/>
<tt> <tt>
Copyright 2005,2009 Ivan SZKIBA<br/> Copyright 2005,2009 Ivan SZKIBA<br/>
@ -160,7 +160,7 @@ nicht jeder Fehler unsererseits behoben werden kann.
See the License for the specific language governing permissions and<br/> See the License for the specific language governing permissions and<br/>
limitations under the License.<br/> limitations under the License.<br/>
</tt> </tt>
<h3>org.json.json</h3> <h2>org.json.json</h2>
<a href="">https://json.org</a><br/> <a href="">https://json.org</a><br/>
<tt> <tt>
Copyright (c) 2018 JSON.org<br/> Copyright (c) 2018 JSON.org<br/>
@ -185,5 +185,35 @@ nicht jeder Fehler unsererseits behoben werden kann.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/> SOFTWARE.<br/>
</tt> </tt>
<h2>com.jgoodies.forms</h2>
<tt>
Copyright (c) 2002-2015 JGoodies Software GmbH. All Rights Reserved.<br/>
<br/>
Redistribution and use in source and binary forms, with or without<br/>
modification, are permitted provided that the following conditions are met:<br/>
<br/>
o Redistributions of source code must retain the above copyright notice,<br/>
this list of conditions and the following disclaimer.<br/>
<br/>
o Redistributions in binary form must reproduce the above copyright notice,<br/>
this list of conditions and the following disclaimer in the documentation<br/>
and/or other materials provided with the distribution.<br/>
<br/>
o Neither the name of JGoodies Software GmbH nor the names of<br/>
its contributors may be used to endorse or promote products derived<br/>
from this software without specific prior written permission.<br/>
<br/>
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"<br/>
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,<br/>
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR<br/>
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR<br/>
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,<br/>
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,<br/>
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;<br/>
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,<br/>
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE<br/>
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,<br/>
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br/>
</tt>
</body> </body>
</html> </html>

View File

@ -102,3 +102,11 @@ star_on_github="Star us" auf GitHub
client_up_to_date=Client ist aktuell client_up_to_date=Client ist aktuell
developer_page=Entwickler-Seite developer_page=Entwickler-Seite
project_page=Projektseite project_page=Projektseite
fast_check=Schnelle Prüfung
intensive_check=Intensive Prüfung
note=Hinweis
presets_note=Vorlagen können nur als Vorlage für neue User-Vorlagen verwendet werden. Du kannst eine eigene Vorlage auf Bases dieser Server Vorlage erstellen. Dabei wird die Liste aler Mods übernommen.
clone_preset=Vorlage klonen
new_modset_name=Modsset Name
modset_exists_msg=Bitte wähle ein anderen Namen für deine Vorlage.
modset_exists=Preset mit diesen Namen existiert bereits

View File

@ -100,3 +100,11 @@ star_on_github=Star us on GitHub
client_up_to_date=Client is up to date client_up_to_date=Client is up to date
developer_page=Developer page developer_page=Developer page
project_page=Project page project_page=Project page
new_modset_name=Modsset name
intensive_check=Intensive check
fast_check=Fast check
note=Note
presets_note=Presets can only be used as preset for new user presents. You can create your own custom preset based on this server preset. The list of all mods will be used.
clone_preset=Clone preset
modset_exists_msg=Please choose another name for your preset.
modset_exists=Preset with these names already exists