396 lines
14 KiB
Java
396 lines
14 KiB
Java
package util;
|
|
|
|
// Java imports
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.image.*;
|
|
import java.io.*;
|
|
import java.net.*;
|
|
import java.nio.charset.*;
|
|
import java.nio.file.*;
|
|
import java.util.*;
|
|
import javax.imageio.*;
|
|
import javax.swing.*;
|
|
import javax.swing.border.*;
|
|
import javax.swing.event.*;
|
|
import javax.swing.plaf.basic.*;
|
|
|
|
// General utility methods
|
|
public final class Util {
|
|
|
|
// This class cannot be instantiated
|
|
private Util() { }
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Classes //
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Event listener interfaces
|
|
public interface OnClose { void call(WindowEvent e); }
|
|
public interface OnClose2 { void call(InternalFrameEvent e); }
|
|
public interface OnFocus { void call(FocusEvent e); }
|
|
public interface OnKey { void call(KeyEvent e); }
|
|
public interface OnMouse { void call(MouseEvent e); }
|
|
public interface OnResize { void call(ComponentEvent e); }
|
|
public interface OnVisible { void call(AncestorEvent e); }
|
|
|
|
// Data class for byte-order marks
|
|
private static class BOM {
|
|
byte[] mark;
|
|
Charset set;
|
|
BOM(byte[] m, Charset s) { mark = m; set = s; }
|
|
}
|
|
|
|
// Font metrics wrapper
|
|
public static class Font extends java.awt.Font {
|
|
public final FontMetrics metrics;
|
|
public Font(java.awt.Font font) {
|
|
super(font);
|
|
metrics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
|
|
.getGraphics().getFontMetrics(this);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Constants //
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Directory from which the JVM was initialized
|
|
public static final File PWD = new File(System.getProperty("user.dir"));
|
|
|
|
// Text file byte-order marks
|
|
private static final BOM[] BOMS = {
|
|
new BOM(new byte[] { -17, -69, -65 }, StandardCharsets.UTF_8 ),
|
|
new BOM(new byte[] { - 2, - 1 }, StandardCharsets.UTF_16BE),
|
|
new BOM(new byte[] { - 1, - 2 }, StandardCharsets.UTF_16LE),
|
|
};
|
|
|
|
// Available font families
|
|
private static final String[] FONTS;
|
|
|
|
// Filesystem state manager for the current .jar (if any)
|
|
private static final FileSystem JARFS;
|
|
|
|
// Static initializer
|
|
static {
|
|
|
|
// Font families
|
|
var fonts = GraphicsEnvironment
|
|
.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
|
|
Arrays.sort(fonts, String.CASE_INSENSITIVE_ORDER);
|
|
FONTS = fonts;
|
|
|
|
// .jar filesystem
|
|
FileSystem fs = null;
|
|
try {
|
|
fs = FileSystems.newFileSystem(
|
|
new URI("jar:" + Util.class.getProtectionDomain()
|
|
.getCodeSource().getLocation().toString()),
|
|
new HashMap<String, String>(),
|
|
Util.class.getClassLoader()
|
|
);
|
|
} catch (Exception e) { }
|
|
JARFS = fs;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
// Static Methods //
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
// Apply an alpha value to a Color object
|
|
public static Color alpha(Color color, float alpha) {
|
|
int a = (int) Math.round(alpha * 255);
|
|
return color == null || a < 0 || a > 255 ? null :
|
|
new Color(color.getRGB() & 0x00FFFFFF | a << 24, true);
|
|
}
|
|
|
|
// Blend one color into another
|
|
public static Color blend(Color front, Color back, float alpha) {
|
|
|
|
// Error checking
|
|
if (front == null || back == null ||
|
|
!Float.isFinite(alpha) || alpha < 0 || alpha > 1)
|
|
return null;
|
|
|
|
// Decompose components
|
|
var colors = new Color[] { front, back };
|
|
var argb = new float[3][4];
|
|
for (int x = 0; x < 2; x++) {
|
|
int bits = colors[x].getRGB();
|
|
for (int y = 3; y >= 0; y++, bits >>= 8)
|
|
argb[x][y] = (bits & 0xFF) / 255.0f;
|
|
}
|
|
|
|
// Combine the colors
|
|
argb[2][0] = argb[0][0] + argb[1][0] * (1 - argb[0][0]);
|
|
for (int x = 1; x < 4; x++)
|
|
argb[2][x] = (
|
|
argb[0][x] * argb[0][0] +
|
|
argb[1][x] * argb[1][0] * (1 - argb[0][0])
|
|
) / argb[2][0];
|
|
|
|
// Produce the resulting Color object
|
|
int bits = 0;
|
|
for (int x = 0; x < 4; x++)
|
|
bits = bits << 8 | (int) Math.round(argb[2][x] * 255);
|
|
return new Color(bits, true);
|
|
}
|
|
|
|
// Read a file from disk
|
|
public static byte[] fileRead(File file) {
|
|
FileInputStream stream = null;
|
|
byte[] data = null;
|
|
try {
|
|
stream = new FileInputStream(file);
|
|
data = stream.readAllBytes();
|
|
} catch (Exception e) { }
|
|
try { stream.close(); } catch (Exception e) { }
|
|
return data;
|
|
}
|
|
|
|
// Read a file, first from disk, then from .jar
|
|
public static byte[] fileRead(String filename) {
|
|
InputStream stream = null;
|
|
|
|
// Open the file on disk
|
|
try { stream = new FileInputStream(filename); }
|
|
|
|
// File on disk could not be opened, so get resource from .jar
|
|
catch (Exception e) {
|
|
stream = Util.class.getResourceAsStream("/" + filename);
|
|
if (stream == null)
|
|
return null; // Resource in .jar could not be found
|
|
}
|
|
|
|
// Read the file data into memory
|
|
byte[] data = null;
|
|
try { data = stream.readAllBytes(); } catch (Exception e) { }
|
|
try { stream.close(); } catch (Exception e) { }
|
|
return data;
|
|
}
|
|
|
|
// Select a font family from a list, if avaiable
|
|
public static String fontFamily(String[] families) {
|
|
for (String family : families)
|
|
if (Arrays.binarySearch(FONTS, family,
|
|
String.CASE_INSENSITIVE_ORDER) >= 0)
|
|
return family;
|
|
return null;
|
|
}
|
|
|
|
// Read an image file as an icon
|
|
public static Icon iconRead(String filename) {
|
|
BufferedImage img = imageRead(filename);
|
|
return img == null ? null : new ImageIcon(img);
|
|
}
|
|
|
|
// Read an image file by filename
|
|
public static BufferedImage imageRead(String filename) {
|
|
try { return
|
|
ImageIO.read(new ByteArrayInputStream(fileRead(filename)));
|
|
} catch (Exception e) { return null; }
|
|
}
|
|
|
|
// Read an image file by handle
|
|
public static BufferedImage imageRead(File file) {
|
|
try { return imageRead(file.getAbsolutePath()); }
|
|
catch (Exception e) { return null; }
|
|
}
|
|
|
|
// Produce a list of files contained in a directory
|
|
// If the directory is not found on disk, looks for it in the .jar
|
|
public static String[] listFiles(String path) {
|
|
|
|
// Check for the directory on disk
|
|
var file = new File(path);
|
|
if (file.exists() && file.isDirectory())
|
|
return file.list();
|
|
|
|
// Not executing out of a .jar
|
|
if (JARFS == null)
|
|
return null;
|
|
|
|
// Check for the directory in the .jar
|
|
try {
|
|
var list = Files.list(JARFS.getPath("/" + path)).toArray();
|
|
var ret = new String[list.length];
|
|
for (int x = 0; x < list.length; x++) {
|
|
path = "/" + ((Path) list[x]).toString();
|
|
ret[x] = path.substring(path.lastIndexOf("/") + 1);
|
|
}
|
|
return ret;
|
|
} catch (Exception e) { }
|
|
|
|
// The directory was not found
|
|
return null;
|
|
}
|
|
|
|
// Produce a list of available font family names
|
|
public static String[] listFonts() {
|
|
var ret = new String[FONTS.length];
|
|
System.arraycopy(FONTS, 0, ret, 0, FONTS.length);
|
|
return ret;
|
|
}
|
|
|
|
// Produce a WindowListener with a functional interface
|
|
public static WindowListener onClose(OnClose close) {
|
|
return new WindowListener() {
|
|
public void windowActivated (WindowEvent e) { }
|
|
public void windowClosed (WindowEvent e) { }
|
|
public void windowDeactivated(WindowEvent e) { }
|
|
public void windowDeiconified(WindowEvent e) { }
|
|
public void windowIconified (WindowEvent e) { }
|
|
public void windowOpened (WindowEvent e) { }
|
|
public void windowClosing (WindowEvent e)
|
|
{ if (close != null) close.call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce an InternalFrameListener with a functional interface
|
|
public static InternalFrameListener onClose2(OnClose2 close) {
|
|
return new InternalFrameListener() {
|
|
public void internalFrameActivated (InternalFrameEvent e) { }
|
|
public void internalFrameClosed (InternalFrameEvent e) { }
|
|
public void internalFrameDeactivated(InternalFrameEvent e) { }
|
|
public void internalFrameDeiconified(InternalFrameEvent e) { }
|
|
public void internalFrameIconified (InternalFrameEvent e) { }
|
|
public void internalFrameOpened (InternalFrameEvent e) { }
|
|
public void internalFrameClosing (InternalFrameEvent e)
|
|
{ if (close != null) close.call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce a FocusListener with functional interfaces
|
|
public static FocusListener onFocus(OnFocus focus, OnFocus blur) {
|
|
return new FocusListener() {
|
|
public void focusGained(FocusEvent e)
|
|
{ if (focus != null) focus.call(e); }
|
|
public void focusLost (FocusEvent e)
|
|
{ if (blur != null) blur .call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce a KeyListener with functional interfaces
|
|
public static KeyListener onKey(OnKey down, OnKey up) {
|
|
return new KeyListener() {
|
|
public void keyTyped (KeyEvent e) { }
|
|
public void keyPressed (KeyEvent e)
|
|
{ if (down != null) down.call(e); }
|
|
public void keyReleased(KeyEvent e)
|
|
{ if (up != null) up .call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce a MouseMotionListener with a functional interface
|
|
public static MouseMotionListener onMouseMove(OnMouse move, OnMouse drag) {
|
|
return new MouseMotionListener() {
|
|
public void mouseMoved (MouseEvent e)
|
|
{ if (move != null) move.call(e); }
|
|
public void mouseDragged(MouseEvent e)
|
|
{ if (drag != null) drag.call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce a MouseListener with functional interfaces
|
|
public static MouseListener onMouse(OnMouse down, OnMouse up) {
|
|
return new MouseListener() {
|
|
public void mouseClicked (MouseEvent e) { }
|
|
public void mouseEntered (MouseEvent e) { }
|
|
public void mouseExited (MouseEvent e) { }
|
|
public void mousePressed (MouseEvent e)
|
|
{ if (down != null) down.call(e); }
|
|
public void mouseReleased(MouseEvent e)
|
|
{ if (up != null) up .call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce a ComponentListener with a functional interface
|
|
public static ComponentListener onResize(OnResize resize) {
|
|
return new ComponentListener() {
|
|
public void componentHidden (ComponentEvent e) { }
|
|
public void componentMoved (ComponentEvent e) { }
|
|
public void componentShown (ComponentEvent e) { }
|
|
public void componentResized(ComponentEvent e)
|
|
{ if (resize != null) resize.call(e); }
|
|
};
|
|
}
|
|
|
|
// Produce an AncestorListener using functional interfaces
|
|
public static AncestorListener onVisible(OnVisible show, OnVisible hide) {
|
|
return new AncestorListener() {
|
|
public void ancestorMoved (AncestorEvent e) { }
|
|
public void ancestorAdded (AncestorEvent e)
|
|
{ if (show != null) show.call(e); }
|
|
public void ancestorRemoved(AncestorEvent e)
|
|
{ if (hide != null) hide.call(e); }
|
|
};
|
|
}
|
|
|
|
// Configure the Swing look-and-feel with the system theme
|
|
public static boolean setSystemLAF() {
|
|
try {
|
|
UIManager.setLookAndFeel(
|
|
UIManager.getSystemLookAndFeelClassName());
|
|
return true;
|
|
} catch (Exception e) { return false; }
|
|
}
|
|
|
|
// Produce a JSplitPane with an un-styled divider
|
|
public static JSplitPane splitPane(int orientation) {
|
|
var split = new JSplitPane(orientation, null, null);
|
|
split.setContinuousLayout(true);
|
|
split.setDividerSize(2);
|
|
split.setUI(new BasicSplitPaneUI() {
|
|
public BasicSplitPaneDivider createDefaultDivider() {
|
|
return new BasicSplitPaneDivider(this) {
|
|
public void setBorder(Border b) { }
|
|
};
|
|
}
|
|
});
|
|
split.setBorder(null);
|
|
return split;
|
|
}
|
|
|
|
// Read a text file, accounting for BOMs
|
|
public static String textRead(String filename) {
|
|
|
|
// Read the file
|
|
byte[] data = fileRead(filename);
|
|
if (data == null)
|
|
return null;
|
|
|
|
// Configure working variables
|
|
byte[] mark = null;
|
|
Charset set = StandardCharsets.ISO_8859_1; // Default charset
|
|
|
|
// Check for a byte-order mark
|
|
outer: for (BOM bom : BOMS) {
|
|
if (data.length < bom.mark.length)
|
|
continue;
|
|
for (int x = 0; x < bom.mark.length; x++)
|
|
if (data[x] != bom.mark[x])
|
|
continue outer;
|
|
mark = bom.mark;
|
|
set = bom.set;
|
|
break;
|
|
}
|
|
|
|
// Remove the byte-order mark from the data
|
|
if (mark != null) {
|
|
byte[] temp = new byte[data.length - mark.length];
|
|
System.arraycopy(data, mark.length, temp, 0, temp.length);
|
|
data = temp;
|
|
}
|
|
|
|
// Produce a string
|
|
return new String(data, set);
|
|
}
|
|
|
|
}
|