Ver código fonte

Adding Configuration Editor GUI

Zoey76 6 anos atrás
pai
commit
d451bf4d92

+ 2 - 1
README.md

@@ -18,4 +18,5 @@ Existing commands
   - update: updates an account
   - list: list the accounts
   - delete: deletes the account
-- quit: exits the CLI
+- quit: exits the CLI
+- config: opens the configuration editor GUI (It's a bonus)

+ 10 - 8
src/main/java/com/l2jserver/cli/L2JServerCLI.java

@@ -25,6 +25,7 @@ import java.util.concurrent.Callable;
 import com.l2jserver.cli.command.AccountCommand;
 import com.l2jserver.cli.command.BuildCommand;
 import com.l2jserver.cli.command.CodeCommand;
+import com.l2jserver.cli.command.ConfigurationEditorGUICommand;
 import com.l2jserver.cli.command.DatabaseCommand;
 import com.l2jserver.cli.command.DeployCommand;
 import com.l2jserver.cli.command.GameServerCommand;
@@ -37,14 +38,15 @@ import picocli.CommandLine.Command;
  * L2J Server CLI.
  * @author Zoey76
  */
-@Command(name = "", version = "1.0", subcommands = { //
-	CodeCommand.class, //
-	BuildCommand.class, //
-	DeployCommand.class, //
-	DatabaseCommand.class, //
-	GameServerCommand.class, //
-	AccountCommand.class, //
-	QuitCommand.class //
+@Command(name = "", version = "1.0", subcommands = {
+	CodeCommand.class,
+	BuildCommand.class,
+	DeployCommand.class,
+	DatabaseCommand.class,
+	GameServerCommand.class,
+	AccountCommand.class,
+	QuitCommand.class,
+	ConfigurationEditorGUICommand.class
 })
 public class L2JServerCLI implements Callable<Void> {
 	

+ 48 - 0
src/main/java/com/l2jserver/cli/command/ConfigurationEditorGUICommand.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2019 L2J Server
+ *
+ * This file is part of L2J Server.
+ *
+ * L2J Server is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * L2J Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.cli.command;
+
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import com.l2jserver.gui.editor.ConfigUserInterface;
+
+import picocli.CommandLine.Command;
+
+/**
+ * Configuration Editor GUI command.
+ * @author Zoey76
+ */
+@Command(name = "config", aliases = "gui")
+public class ConfigurationEditorGUICommand extends AbstractCommand {
+	
+	@Override
+	public void run() {
+		try {
+			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+		} catch (Exception ex) {
+			ex.printStackTrace();
+		}
+		
+		SwingUtilities.invokeLater(() -> {
+			ConfigUserInterface cui = new ConfigUserInterface();
+			cui.setVisible(true);
+		});
+	}
+}

+ 576 - 0
src/main/java/com/l2jserver/gui/editor/ConfigUserInterface.java

@@ -0,0 +1,576 @@
+/*
+ * Copyright © 2019 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.gui.editor;
+
+import static java.awt.GridBagConstraints.BOTH;
+import static java.awt.GridBagConstraints.FIRST_LINE_START;
+import static java.awt.GridBagConstraints.HORIZONTAL;
+import static java.awt.GridBagConstraints.NONE;
+import static javax.swing.JFileChooser.APPROVE_OPTION;
+import static javax.swing.JFileChooser.DIRECTORIES_ONLY;
+import static javax.swing.JOptionPane.ERROR_MESSAGE;
+import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextArea;
+import javax.swing.JToolBar;
+import javax.swing.SwingConstants;
+import javax.swing.ToolTipManager;
+import javax.swing.UIManager;
+
+import com.l2jserver.gui.editor.ConfigUserInterface.ConfigFile.ConfigComment;
+import com.l2jserver.gui.editor.ConfigUserInterface.ConfigFile.ConfigProperty;
+
+/**
+ * Configuration Editor GUI.
+ * @author KenM
+ * @author Zoey76
+ */
+public class ConfigUserInterface extends JFrame implements ActionListener {
+	private static final long serialVersionUID = 2609592249095305857L;
+	
+	private static final String IMAGE_PATH = "/images/";
+	
+	private static final String EOL = System.lineSeparator();
+	
+	private final JTabbedPane _tabPane = new JTabbedPane();
+	
+	private final JFileChooser fileChooser;
+	
+	private List<ConfigFile> _configs = new ArrayList<>();
+	
+	public ConfigUserInterface() {
+		setTitle("Server Configuration Tool");
+		setDefaultCloseOperation(EXIT_ON_CLOSE);
+		this.setSize(750, 500);
+		setLayout(new GridBagLayout());
+		
+		setDefaultLookAndFeelDecorated(true);
+		setIconImage(new ImageIcon(getClass().getResource(IMAGE_PATH + "l2j.png")).getImage());
+		
+		fileChooser = new JFileChooser("Open");
+		fileChooser.setFileSelectionMode(DIRECTORIES_ONLY);
+		fileChooser.setCurrentDirectory(new File("."));
+		
+		GridBagConstraints cons = new GridBagConstraints();
+		cons.fill = HORIZONTAL;
+		cons.gridx = 0;
+		cons.gridy = 0;
+		cons.weighty = 0;
+		cons.weightx = 1;
+		
+		JMenuBar menubar = new JMenuBar();
+		
+		JMenu fileMenu = new JMenu("File");
+		JMenu helpMenu = new JMenu("Help");
+		
+		JMenuItem exitItem = new JMenuItem("Exit");
+		exitItem.setActionCommand("exit");
+		exitItem.addActionListener(this);
+		fileMenu.add(exitItem);
+		
+		JMenuItem aboutItem = new JMenuItem("About");
+		aboutItem.setIcon(new ImageIcon(getClass().getResource(IMAGE_PATH + "help.png")));
+		aboutItem.setActionCommand("about");
+		aboutItem.addActionListener(this);
+		helpMenu.add(aboutItem);
+		
+		menubar.add(fileMenu);
+		menubar.add(helpMenu);
+		
+		setJMenuBar(menubar);
+		
+		JToolBar toolBar = new JToolBar();
+		toolBar.setFloatable(false);
+		toolBar.setRollover(true);
+		toolBar.add(createToolButton("disk.png", "Save", "save"));
+		this.add(toolBar, cons);
+		
+		cons.gridy++;
+		cons.fill = BOTH;
+		cons.weighty = 1;
+		loadConfigs();
+		buildInterface();
+		this.add(_tabPane, cons);
+	}
+	
+	private JButton createToolButton(String image, String text, String action) {
+		JButton button = new JButton(text, new ImageIcon(getClass().getResource(IMAGE_PATH + image)));
+		button.setActionCommand(action);
+		button.addActionListener(this);
+		return button;
+	}
+	
+	private void buildInterface() {
+		ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE);
+		ToolTipManager.sharedInstance().setInitialDelay(0);
+		ToolTipManager.sharedInstance().setReshowDelay(0);
+		
+		GridBagConstraints cons = new GridBagConstraints();
+		cons.fill = NONE;
+		cons.anchor = FIRST_LINE_START;
+		cons.insets = new Insets(2, 2, 2, 2);
+		for (ConfigFile cf : getConfigs()) {
+			JPanel panel = new JPanel() {
+				private static final long serialVersionUID = -323928678804839054L;
+				
+				@Override
+				public void scrollRectToVisible(Rectangle r) {
+				}
+			};
+			panel.setLayout(new GridBagLayout());
+			
+			cons.gridy = 0;
+			cons.weighty = 0;
+			for (ConfigComment cc : cf.getConfigProperties()) {
+				if (!(cc instanceof ConfigProperty)) {
+					continue;
+				}
+				
+				ConfigProperty cp = (ConfigProperty) cc;
+				cons.gridx = 0;
+				
+				JLabel keyLabel = new JLabel(cp.getDisplayName() + ':', new ImageIcon(getClass().getResource(IMAGE_PATH + "help.png")), SwingConstants.LEFT);
+				String comments = "<b>" + cp.getName() + ":</b><br>" + cp.getComments();
+				comments = comments.replace(EOL, "<br>");
+				comments = "<html>" + comments + "</html>";
+				keyLabel.setToolTipText(comments);
+				cons.weightx = 0;
+				panel.add(keyLabel, cons);
+				cons.gridx++;
+				
+				JComponent valueComponent = cp.getValueComponent();
+				valueComponent.setToolTipText(comments);
+				cons.weightx = 1;
+				panel.add(valueComponent, cons);
+				cons.gridx++;
+				cons.gridy++;
+			}
+			cons.gridy++;
+			cons.weighty = 1;
+			panel.add(new JLabel(), cons); // filler
+			_tabPane.addTab(cf.getName(), new JScrollPane(panel));
+		}
+	}
+	
+	private void loadConfigs() {
+		final var returnVal = fileChooser.showOpenDialog(this);
+		if (returnVal != APPROVE_OPTION) {
+			JOptionPane.showMessageDialog(ConfigUserInterface.this, "Must select a configuration folder!", "Error", ERROR_MESSAGE);
+			return;
+		}
+		
+		final File configsDir = fileChooser.getSelectedFile();
+		final File[] files = configsDir.listFiles();
+		if (files == null) {
+			JOptionPane.showMessageDialog(ConfigUserInterface.this, "Error reading " + "config", "Error", ERROR_MESSAGE);
+			return;
+		}
+		
+		for (File file : files) {
+			if (file.getName().endsWith(".properties") && file.isFile() && file.canWrite()) {
+				try {
+					parsePropertiesFile(file);
+				} catch (IOException e) {
+					JOptionPane.showMessageDialog(ConfigUserInterface.this, "Error reading " + file.getName(), "Error", ERROR_MESSAGE);
+					System.exit(3);
+				}
+			}
+		}
+	}
+	
+	private void parsePropertiesFile(File file) throws IOException {
+		try (FileInputStream fis = new FileInputStream(file);
+			InputStreamReader isr = new InputStreamReader(fis);
+			LineNumberReader lnr = new LineNumberReader(isr)) {
+			String line;
+			StringBuilder commentBuffer = new StringBuilder();
+			ConfigFile cf = new ConfigFile(file);
+			while ((line = lnr.readLine()) != null) {
+				line = line.trim();
+				if (line.isEmpty()) {
+					// blank line, reset comments
+					if (commentBuffer.length() > 0) {
+						cf.addConfigComment(commentBuffer.toString());
+					}
+					commentBuffer.setLength(0);
+				} else if (line.charAt(0) == '#') {
+					if (commentBuffer.length() > 0) {
+						commentBuffer.append(EOL);
+					}
+					commentBuffer.append(line.substring(1));
+				} else if (line.indexOf('=') >= 0) {
+					String[] kv = line.split("=", 2);
+					String key = kv[0].trim();
+					StringBuilder value = new StringBuilder();
+					if (kv.length > 1) {
+						value.append(kv[1].trim());
+					}
+					
+					if (line.indexOf('\\') >= 0) {
+						while (((line = lnr.readLine()) != null) && (line.indexOf('\\') >= 0)) {
+							value.append(EOL + line);
+						}
+						value.append(EOL + line);
+					}
+					
+					String comments = commentBuffer.toString();
+					commentBuffer.setLength(0); // reset
+					
+					cf.addConfigProperty(key, parseValue(value.toString()), comments);
+				}
+			}
+			getConfigs().add(cf);
+		}
+	}
+	
+	private Object parseValue(String value) {
+		if (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("true")) {
+			return Boolean.parseBoolean(value);
+		}
+		
+		if (value.equals("localhost")) {
+			value = "127.0.0.1";
+		}
+		
+		String[] parts = value.split("\\.");
+		if (parts.length == 4) {
+			boolean ok = true;
+			for (int i = 0; (i < 4) && ok; i++) {
+				try {
+					int parseInt = Integer.parseInt(parts[i]);
+					if ((parseInt < 0) || (parseInt > 255)) {
+						ok = false;
+					}
+				} catch (NumberFormatException e) {
+					ok = false;
+				}
+			}
+			
+			if (ok) {
+				try {
+					InetAddress address = InetAddress.getByName(value);
+					return address;
+				} catch (UnknownHostException e) {
+					// ignore
+				}
+			}
+		}
+		
+		return value;
+	}
+	
+	static class ConfigFile {
+		private final File _file;
+		private String _name;
+		private final List<ConfigComment> _configs = new ArrayList<>();
+		
+		public ConfigFile(File file) {
+			_file = file;
+			int lastIndex = file.getName().lastIndexOf('.');
+			setName(file.getName().substring(0, lastIndex));
+		}
+		
+		public void addConfigProperty(String name, Object value, ValueType type, String comments) {
+			_configs.add(new ConfigProperty(name, value, type, comments));
+		}
+		
+		public void addConfigComment(String comment) {
+			_configs.add(new ConfigComment(comment));
+		}
+		
+		public void addConfigProperty(String name, Object value, String comments) {
+			this.addConfigProperty(name, value, ValueType.firstTypeMatch(value), comments);
+		}
+		
+		public List<ConfigComment> getConfigProperties() {
+			return _configs;
+		}
+		
+		public void setName(String name) {
+			_name = name;
+		}
+		
+		public String getName() {
+			return _name;
+		}
+		
+		public void save() throws IOException {
+			try (FileOutputStream fos = new FileOutputStream(_file);
+				OutputStreamWriter osw = new OutputStreamWriter(fos);
+				BufferedWriter bufWriter = new BufferedWriter(osw)) {
+				for (ConfigComment cc : _configs) {
+					cc.save(bufWriter);
+				}
+			}
+		}
+		
+		class ConfigComment {
+			
+			private String _comments;
+			
+			public ConfigComment(String comments) {
+				_comments = comments;
+			}
+			
+			public String getComments() {
+				return _comments;
+			}
+			
+			public void setComments(String comments) {
+				_comments = comments;
+			}
+			
+			public void save(Writer writer) throws IOException {
+				StringBuilder sb = new StringBuilder();
+				sb.append('#');
+				sb.append(getComments().replace(EOL, EOL + "#"));
+				sb.append(EOL + EOL);
+				writer.write(sb.toString());
+			}
+		}
+		
+		class ConfigProperty extends ConfigComment {
+			private String _propname;
+			private Object _value;
+			private ValueType _type;
+			private JComponent _component;
+			
+			public ConfigProperty(String name, Object value, ValueType type, String comments) {
+				super(comments);
+				if (!type.getType().isAssignableFrom(value.getClass())) {
+					throw new IllegalArgumentException("Value Instance Type doesn't match the type argument.");
+				}
+				_propname = name;
+				_type = type;
+				_value = value;
+			}
+			
+			public String getName() {
+				return _propname;
+			}
+			
+			public String getDisplayName() {
+				return unCamelize(_propname);
+			}
+			
+			public void setName(String name) {
+				_propname = name;
+			}
+			
+			public Object getValue() {
+				return _value;
+			}
+			
+			public void setValue(String value) {
+				_value = value;
+			}
+			
+			public ValueType getType() {
+				return _type;
+			}
+			
+			public void setType(ValueType type) {
+				_type = type;
+			}
+			
+			public JComponent getValueComponent() {
+				if (_component == null) {
+					_component = createValueComponent();
+				}
+				return _component;
+			}
+			
+			public JComponent createValueComponent() {
+				switch (getType()) {
+					case BOOLEAN:
+						boolean bool = (Boolean) getValue();
+						JCheckBox checkBox = new JCheckBox();
+						checkBox.setSelected(bool);
+						return checkBox;
+					case IPv4:
+						return new JIPTextField((Inet4Address) getValue());
+					case DOUBLE:
+					case INTEGER:
+					case STRING:
+					default:
+						String val = getValue().toString();
+						JTextArea textArea = new JTextArea(val);
+						textArea.setFont(UIManager.getFont("TextField.font"));
+						int rows = 1;
+						for (int i = 0; i < val.length(); i++) {
+							if (val.charAt(i) == '\\') {
+								rows++;
+							}
+						}
+						textArea.setRows(rows);
+						textArea.setColumns(Math.max(val.length() / rows, 20));
+						return textArea;
+				}
+			}
+			
+			@Override
+			public void save(Writer writer) throws IOException {
+				String value;
+				if (getValueComponent() instanceof JCheckBox) {
+					value = Boolean.toString(((JCheckBox) getValueComponent()).isSelected());
+					value = value.substring(0, 1).toUpperCase() + value.substring(1);
+				} else if (getValueComponent() instanceof JIPTextField) {
+					value = ((JIPTextField) getValueComponent()).getText();
+				} else if (getValueComponent() instanceof JTextArea) {
+					value = ((JTextArea) getValueComponent()).getText();
+				} else {
+					throw new IllegalStateException("Unhandled component value");
+				}
+				
+				StringBuilder sb = new StringBuilder();
+				sb.append('#');
+				sb.append(getComments().replace(EOL, EOL + "#"));
+				sb.append(EOL);
+				sb.append(getName());
+				sb.append(" = ");
+				sb.append(value);
+				sb.append(EOL);
+				sb.append(EOL);
+				writer.write(sb.toString());
+			}
+		}
+	}
+	
+	public static enum ValueType {
+		BOOLEAN(Boolean.class),
+		DOUBLE(Double.class),
+		INTEGER(Integer.class),
+		IPv4(Inet4Address.class),
+		STRING(String.class);
+		
+		private final Class<?> _type;
+		
+		private ValueType(Class<?> type) {
+			_type = type;
+		}
+		
+		public Class<?> getType() {
+			return _type;
+		}
+		
+		public static ValueType firstTypeMatch(Object value) {
+			for (ValueType vt : ValueType.values()) {
+				if (vt.getType() == value.getClass()) {
+					return vt;
+				}
+			}
+			throw new NoSuchElementException("No match for: " + value.getClass().getName());
+		}
+	}
+	
+	@Override
+	public void actionPerformed(ActionEvent e) {
+		String cmd = e.getActionCommand();
+		
+		StringBuilder errors = new StringBuilder();
+		
+		if (cmd.equals("save")) {
+			for (ConfigFile cf : ConfigUserInterface.this.getConfigs()) {
+				try {
+					cf.save();
+				} catch (Exception e1) {
+					e1.printStackTrace();
+					errors.append("Error saving" + cf.getName() + ".properties. " + "Reason:" + e1.getLocalizedMessage() + EOL);
+				}
+			}
+			if (errors.length() == 0) {
+				JOptionPane.showMessageDialog(ConfigUserInterface.this, "Configuration saved successfully", "OK", INFORMATION_MESSAGE);
+			} else {
+				JOptionPane.showMessageDialog(ConfigUserInterface.this, errors, "Error", ERROR_MESSAGE);
+				System.exit(2);
+			}
+		} else if (cmd.equals("exit")) {
+			System.exit(0);
+		} else if (cmd.equals("about")) {
+			JOptionPane.showMessageDialog(ConfigUserInterface.this, "© 2019 L2J Team. All rights reserved." + EOL + //
+				"http://www.l2jserver.com" + EOL + EOL + "Icons by http://www.famfamfam.com" + EOL + EOL + "Language: English" + EOL //
+				+ "Translation: L2J Team", "About", INFORMATION_MESSAGE, new ImageIcon(getClass().getResource(IMAGE_PATH + "l2jserverlogo.png")));
+		}
+	}
+	
+	public void setConfigs(List<ConfigFile> configs) {
+		_configs = configs;
+	}
+	
+	public List<ConfigFile> getConfigs() {
+		return _configs;
+	}
+	
+	/**
+	 * @param keyName
+	 * @return Returns the configuration setting name in a human readable form.
+	 */
+	public static String unCamelize(final String keyName) {
+		Pattern p = Pattern.compile("\\p{Lu}");
+		Matcher m = p.matcher(keyName);
+		StringBuffer sb = new StringBuffer();
+		int last = 0;
+		while (m.find()) {
+			if (m.start() != (last + 1)) {
+				m.appendReplacement(sb, " " + m.group());
+			}
+			last = m.start();
+		}
+		m.appendTail(sb);
+		return sb.toString().trim();
+	}
+}

+ 247 - 0
src/main/java/com/l2jserver/gui/editor/JIPTextField.java

@@ -0,0 +1,247 @@
+/*
+ * Copyright © 2019 L2J Server
+ * 
+ * This file is part of L2J Server.
+ * 
+ * L2J Server is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * L2J Server is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.l2jserver.gui.editor;
+
+import java.awt.Component;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.Toolkit;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.PlainDocument;
+
+/**
+ * @author KenM
+ */
+public class JIPTextField extends JPanel implements FocusListener {
+	private static final long serialVersionUID = 1L;
+	private JTextField[] _textFields;
+	private List<FocusListener> _focusListeners;
+	
+	public JIPTextField(String textIp) {
+		super.addFocusListener(this);
+		
+		initIPTextField(textIp);
+		
+		for (JTextField _textField : _textFields) {
+			_textField.addFocusListener(this);
+		}
+	}
+	
+	public JIPTextField() {
+		this("...");
+	}
+	
+	public JIPTextField(Inet4Address value) {
+		this(value.getHostAddress());
+	}
+	
+	private void initIPTextField(String textIp) {
+		final ActionListener nextfocusaction = evt -> ((Component) evt.getSource()).transferFocus();
+		
+		setLayout(new GridBagLayout());
+		_textFields = new JTextField[4];
+		
+		GridBagConstraints cons = new GridBagConstraints();
+		cons.anchor = GridBagConstraints.PAGE_START;
+		cons.fill = GridBagConstraints.HORIZONTAL;
+		cons.insets = new Insets(1, 1, 1, 1);
+		cons.gridx = 0;
+		cons.gridy = 0;
+		
+		MaxLengthDocument previous = null;
+		String[] parts = textIp.split("\\.");
+		for (int i = 0; i < 4; i++) {
+			String str = parts[i];
+			if (i > 0) {
+				JLabel dot = new JLabel(".");
+				cons.weightx = 0;
+				add(dot, cons);
+				cons.gridx++;
+			}
+			MaxLengthDocument maxDoc = new MaxLengthDocument(3);
+			_textFields[i] = new JTextField(maxDoc, str, 3);
+			if (previous != null) {
+				previous.setNext(_textFields[i]);
+			}
+			previous = maxDoc;
+			add(_textFields[i], cons);
+			_textFields[i].addActionListener(nextfocusaction);
+			cons.gridx++;
+		}
+	}
+	
+	@Override
+	public void addFocusListener(FocusListener fl) {
+		if (_focusListeners == null) {
+			_focusListeners = new LinkedList<>();
+		}
+		
+		if ((fl != null) && !_focusListeners.contains(fl)) {
+			_focusListeners.add(fl);
+		}
+	}
+	
+	@Override
+	public void removeFocusListener(FocusListener fl) {
+		if (_focusListeners != null) {
+			_focusListeners.remove(fl);
+		}
+	}
+	
+	public String getText() {
+		StringBuilder str = new StringBuilder();
+		for (int i = 0; i < 4; i++) {
+			if (_textFields[i].getText().length() == 0) {
+				str.append('0');
+			} else {
+				str.append(_textFields[i].getText());
+			}
+			
+			if (i < 3) {
+				str.append('.');
+			}
+		}
+		return str.toString();
+	}
+	
+	public void setText(String str) {
+		try {
+			InetAddress ip = InetAddress.getByName(str);
+			byte b[] = ip.getAddress();
+			for (int i = 0; i < 4; i++) {
+				// byte always have a sign in Java, IP addresses aren't
+				if (b[i] >= 0) {
+					_textFields[i].setText(Byte.toString(b[i]));
+				} else {
+					_textFields[i].setText(Integer.toString(b[i] + 256));
+				}
+			}
+			return;
+		} catch (UnknownHostException ex) {
+		} catch (NullPointerException npe) {
+		}
+		for (int i = 0; i < 4; i++) {
+			_textFields[i].setText("");
+		}
+	}
+	
+	@Override
+	public void setEnabled(boolean enabled) {
+		for (JTextField _textField : _textFields) {
+			if (_textField != null) {
+				_textField.setEnabled(enabled);
+			}
+		}
+	}
+	
+	public boolean isEmpty() {
+		for (int i = 0; i < 4; i++) {
+			if (!_textFields[i].getText().isEmpty()) {
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	public boolean isCorrect() {
+		for (int i = 0; i < 4; i++) {
+			if (_textFields[i].getText().length() == 0) {
+				return false;
+			}
+		}
+		return true;
+	}
+	
+	@Override
+	public void focusGained(FocusEvent event) {
+		if (_focusListeners != null) {
+			for (FocusListener fl : _focusListeners) {
+				fl.focusGained(event);
+			}
+		}
+	}
+	
+	@Override
+	public void focusLost(FocusEvent event) {
+		if (isCorrect() || isEmpty()) {
+			if (_focusListeners != null) {
+				for (FocusListener fl : _focusListeners) {
+					fl.focusLost(event);
+				}
+			}
+		}
+	}
+	
+	public class MaxLengthDocument extends PlainDocument {
+		
+		private static final long serialVersionUID = 1L;
+		
+		private final int _max;
+		private JTextField _next;
+		
+		public MaxLengthDocument(int maxLength) {
+			this(maxLength, null);
+		}
+		
+		public MaxLengthDocument(int maxLength, JTextField next) {
+			_max = maxLength;
+			setNext(next);
+		}
+		
+		@Override
+		public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
+			if ((getLength() + str.length()) > _max) {
+				if (getNext() != null) {
+					if (getNext().getText().length() > 0) {
+						getNext().select(0, getNext().getText().length());
+					} else {
+						getNext().getDocument().insertString(0, str, a);
+					}
+					getNext().requestFocusInWindow();
+				} else {
+					Toolkit.getDefaultToolkit().beep();
+				}
+			} else {
+				super.insertString(offset, str, a);
+			}
+		}
+		
+		public void setNext(JTextField next) {
+			_next = next;
+		}
+		
+		public JTextField getNext() {
+			return _next;
+		}
+	}
+}

BIN
src/main/resources/images/disk.png


BIN
src/main/resources/images/help.png


BIN
src/main/resources/images/l2j.png


BIN
src/main/resources/images/l2jserverlogo.png