diff --git a/core/src/main/java/info/openrocket/core/database/Databases.java b/core/src/main/java/info/openrocket/core/database/Databases.java index cf9c8c0b4..a386ac2ec 100644 --- a/core/src/main/java/info/openrocket/core/database/Databases.java +++ b/core/src/main/java/info/openrocket/core/database/Databases.java @@ -1,5 +1,6 @@ package info.openrocket.core.database; +import info.openrocket.core.material.MaterialGroup; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,89 +41,89 @@ public class Databases { static { // Add default materials - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Acrylic", 1190)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Aluminum", 2700)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Balsa", 170)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Basswood", 500)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Birch", 670)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Brass", 8600)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cardboard", 680)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Carbon fiber", 1780)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cork", 240)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Delrin", 1420)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Depron (XPS)", 40)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Fiberglass", 1850)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Kraft phenolic", 950)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Maple", 755)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Nylon", 1150)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Paper (office)", 820)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Pine", 530)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Plywood (birch)", 630)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polycarbonate (Lexan)", 1200)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polystyrene", 1050)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PVC", 1390)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Spruce", 450)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Steel", 7850)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam (generic EPS)", 20)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam \"Blue foam\" (XPS)", 32)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Titanium", 4500)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Quantum tubing", 1050)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Blue tube", 1300)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PLA - 100% infill", 1250)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PETG - 100% infill", 1250)); - BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "ABS - 100% infill", 1050)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Acrylic", 1190, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Aluminum", 2700, MaterialGroup.METALS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Balsa", 170, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Basswood", 500, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Birch", 670, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Brass", 8600, MaterialGroup.METALS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cardboard", 680, MaterialGroup.PAPER)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Carbon fiber", 1780, MaterialGroup.COMPOSITES)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cork", 240, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Delrin", 1420, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Depron (XPS)", 40, MaterialGroup.FOAMS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Fiberglass", 1850, MaterialGroup.COMPOSITES)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Kraft phenolic", 950, MaterialGroup.COMPOSITES)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Maple", 755, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Nylon", 1150, MaterialGroup.FIBERS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Paper (office)", 820, MaterialGroup.PAPER)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Pine", 530, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Plywood (birch)", 630, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polycarbonate (Lexan)", 1200, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polystyrene", 1050, MaterialGroup.FOAMS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PVC", 1390, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Spruce", 450, MaterialGroup.WOODS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Steel", 7850, MaterialGroup.METALS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam (generic EPS)", 20, MaterialGroup.FOAMS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam \"Blue foam\" (XPS)", 32, MaterialGroup.FOAMS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Titanium", 4500, MaterialGroup.METALS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Quantum tubing", 1050, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Blue tube", 1300, MaterialGroup.COMPOSITES)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PLA - 100% infill", 1250, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PETG - 100% infill", 1250, MaterialGroup.PLASTICS)); + BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "ABS - 100% infill", 1050, MaterialGroup.PLASTICS)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Mylar", 0.021)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (thin)", 0.015)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (heavy)", 0.040)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Silk", 0.060)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Paper (office)", 0.080)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cellophane", 0.018)); - SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cr\u00eape paper", 0.025)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Ripstop nylon", 0.067, MaterialGroup.FABRICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Mylar", 0.021, MaterialGroup.PLASTICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (thin)", 0.015, MaterialGroup.PLASTICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (heavy)", 0.040, MaterialGroup.PLASTICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Silk", 0.060, MaterialGroup.FABRICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Paper (office)", 0.080, MaterialGroup.PAPER)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cellophane", 0.018, MaterialGroup.PLASTICS)); + SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cr\u00eape paper", 0.025, MaterialGroup.PAPER)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Thread (heavy-duty)", 0.0003)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (round 2 mm, 1/16 in)", 0.0018)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 6 mm, 1/4 in)", 0.0043)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 12 mm, 1/2 in)", 0.008)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 19 mm, 3/4 in)", 0.0012)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 25 mm, 1 in)", 0.0016)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Braided nylon (2 mm, 1/16 in)", 0.001)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Braided nylon (3 mm, 1/8 in)", 0.0035)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (11 mm, 7/16 in)", 0.013)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (14 mm, 9/16 in)", 0.016)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (25 mm, 1 in)", 0.029)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 138 (0.4 mm, 1/64 in)", 0.00014808)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 207 (0.5 mm, 1/64 in)", 0.00023622)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 346 (0.7 mm, 1/32 in)", 0.00047243)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 415 (0.8 mm, 1/32 in)", 0.00055117)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 800 (1.1 mm, 3/64 in)", 0.00099211)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (3.2 mm, 1/8 in)", 0.00967306)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (4.8 mm, 3/16 in)", 0.01785797)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (6.4 mm, 1/4 in)", 0.02976328)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (7.9 mm, 5/16 in)", 0.04464491)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (10 mm, 3/8 in)", 0.05952655)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (11 mm, 7/16 in)", 0.07440819)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (13 mm, 1/2 in)", 0.11607678)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (14 mm, 9/16 in)", 0.20834293)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (16 mm, 5/8 in)", 0.28721562)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (19 mm, 3/4 in)", 0.3497185)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (25 mm, 1 in)", 0.45686629)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (10 mm, 3/8 in)", 0.00951444)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (13 mm, 1/2 in)", 0.01334208)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (16 mm, 5/8 in)", 0.01618548)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing lg. (14 mm, 9/16 in)", 0.02723097)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing lg. (25 mm, 1 in)", 0.03969816)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Paraline small IIIA (6.4 mm, 1.4 in)", 0.00371829)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic rubber band (flat 3.2 mm, 1/8 in)", 0.00297638)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic rubber band (flat 6.4 mm, 1/4 in)", 0.00613107)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 3.2 mm, 1/8 in)", 0.00106)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 4 mm, 5/32 in)", 0.002)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 6.4 mm, 1/4 in)", 0.00254)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (round 2 mm, 1/16 in)", 0.0035)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (round 2.5 mm, 3/32 in)", 0.0038)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 10 mm, 3/8 in)", 0.00381)); - LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 13 mm, 1/2 in)", 0.00551172)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Thread (heavy-duty)", 0.0003, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (round 2 mm, 1/16 in)", 0.0018, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 6 mm, 1/4 in)", 0.0043, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 12 mm, 1/2 in)", 0.008, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 19 mm, 3/4 in)", 0.0012, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic cord (flat 25 mm, 1 in)", 0.0016, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Braided nylon (2 mm, 1/16 in)", 0.001, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Braided nylon (3 mm, 1/8 in)", 0.0035, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (11 mm, 7/16 in)", 0.013, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (14 mm, 9/16 in)", 0.016, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Tubular nylon (25 mm, 1 in)", 0.029, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 138 (0.4 mm, 1/64 in)", 0.00014808, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 207 (0.5 mm, 1/64 in)", 0.00023622, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 346 (0.7 mm, 1/32 in)", 0.00047243, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 415 (0.8 mm, 1/32 in)", 0.00055117, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar thread 800 (1.1 mm, 3/64 in)", 0.00099211, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (3.2 mm, 1/8 in)", 0.00967306, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (4.8 mm, 3/16 in)", 0.01785797, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (6.4 mm, 1/4 in)", 0.02976328, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (7.9 mm, 5/16 in)", 0.04464491, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (10 mm, 3/8 in)", 0.05952655, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (11 mm, 7/16 in)", 0.07440819, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (13 mm, 1/2 in)", 0.11607678, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (14 mm, 9/16 in)", 0.20834293, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (16 mm, 5/8 in)", 0.28721562, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (19 mm, 3/4 in)", 0.3497185, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Kevlar 12-strand (25 mm, 1 in)", 0.45686629, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (10 mm, 3/8 in)", 0.00951444, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (13 mm, 1/2 in)", 0.01334208, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing md. (16 mm, 5/8 in)", 0.01618548, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing lg. (14 mm, 9/16 in)", 0.02723097, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Nylon flat webbing lg. (25 mm, 1 in)", 0.03969816, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Paraline small IIIA (6.4 mm, 1.4 in)", 0.00371829, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic rubber band (flat 3.2 mm, 1/8 in)", 0.00297638, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic rubber band (flat 6.4 mm, 1/4 in)", 0.00613107, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 3.2 mm, 1/8 in)", 0.00106, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 4 mm, 5/32 in)", 0.002, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 6.4 mm, 1/4 in)", 0.00254, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (round 2 mm, 1/16 in)", 0.0035, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (round 2.5 mm, 3/32 in)", 0.0038, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 10 mm, 3/8 in)", 0.00381, MaterialGroup.THREADS_LINES)); + LINE_MATERIAL.add(newMaterial(Material.Type.LINE, "Elastic braided cord (flat 13 mm, 1/2 in)", 0.00551172, MaterialGroup.THREADS_LINES)); // Add user-defined materials @@ -159,9 +160,13 @@ public class Databases { * @param density density * @return a new object with the material data */ - private static Material newMaterial(Type type, String baseName, double density) { + private static Material newMaterial(Type type, String baseName, double density, MaterialGroup group) { String name = trans.get("material", baseName); - return Material.newMaterial(type, name, density, false); + return Material.newMaterial(type, name, density, group, false); + } + + private static Material newMaterial(Type type, String baseName, double density) { + return newMaterial(type, baseName, density, null); } diff --git a/core/src/main/java/info/openrocket/core/material/Material.java b/core/src/main/java/info/openrocket/core/material/Material.java index 3230e1647..0b18dda95 100644 --- a/core/src/main/java/info/openrocket/core/material/Material.java +++ b/core/src/main/java/info/openrocket/core/material/Material.java @@ -5,6 +5,8 @@ import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; import info.openrocket.core.unit.UnitGroup; import info.openrocket.core.util.MathUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A class for different material types. Each material has a name and density. @@ -17,8 +19,8 @@ import info.openrocket.core.util.MathUtil; */ public abstract class Material implements Comparable { - private static final Translator trans = Application.getTranslator(); + private static final Logger log = LoggerFactory.getLogger(Material.class); public enum Type { BULK("Databases.materials.types.Bulk", UnitGroup.UNITS_DENSITY_BULK), @@ -48,6 +50,10 @@ public abstract class Material implements Comparable { ///// Definitions of different material types ///// public static class Line extends Material { + Line(String name, double density, MaterialGroup group, boolean userDefined) { + super(name, density, group, userDefined); + } + Line(String name, double density, boolean userDefined) { super(name, density, userDefined); } @@ -59,7 +65,11 @@ public abstract class Material implements Comparable { } public static class Surface extends Material { - + + Surface(String name, double density, MaterialGroup group, boolean userDefined) { + super(name, density, group, userDefined); + } + Surface(String name, double density, boolean userDefined) { super(name, density, userDefined); } @@ -76,6 +86,10 @@ public abstract class Material implements Comparable { } public static class Bulk extends Material { + Bulk(String name, double density, MaterialGroup group, boolean userDefined) { + super(name, density, group, userDefined); + } + Bulk(String name, double density, boolean userDefined) { super(name, density, userDefined); } @@ -88,6 +102,10 @@ public abstract class Material implements Comparable { public static class Custom extends Material { + Custom(String name, double density, MaterialGroup group, boolean userDefined) { + super(name, density, group, userDefined); + } + Custom(String name, double density, boolean userDefined) { super(name, density, userDefined); } @@ -103,20 +121,26 @@ public abstract class Material implements Comparable { private final String name; private final double density; private final boolean userDefined; + private final MaterialGroup group; /** * Constructor for materials. * * @param name ignored when defining system materials. - * @param key ignored when defining user materials. - * @param density + * @param density: the density of the material. + * @param group the material group. * @param userDefined true if this is a user defined material, false if it is a system material. */ - private Material(String name, double density, boolean userDefined) { + private Material(String name, double density, MaterialGroup group, boolean userDefined) { this.name = name; - this.userDefined = userDefined; this.density = density; + this.group = group; + this.userDefined = userDefined; + } + + private Material(String name, double density, boolean userDefined) { + this(name, density, null, userDefined); } public double getDensity() { @@ -136,6 +160,17 @@ public abstract class Material implements Comparable { } public abstract Type getType(); + + public MaterialGroup getGroup() { + return group; + } + + public int getGroupPriority() { + if (group == null) { + return Integer.MAX_VALUE; + } + return group.getPriority(); + } @Override public String toString() { @@ -154,7 +189,14 @@ public abstract class Material implements Comparable { if (this.getClass() != o.getClass()) return false; Material m = (Material) o; - return ((m.name.equals(this.name)) && MathUtil.equals(m.density, this.density)); + return ((m.name.equals(this.name)) && MathUtil.equals(m.density, this.density)) && groupsEqual(m); + } + + private boolean groupsEqual(Material m) { + if (group == null) { + return m.group == null; + } + return group.equals(m.group); } @@ -187,37 +229,37 @@ public abstract class Material implements Comparable { * @param type the material type * @param name the material name * @param density the material density + * @param group the material group * @param userDefined whether the material is user-defined or not * @return the new material */ + public static Material newMaterial(Type type, String name, double density, MaterialGroup group, boolean userDefined) { + return switch (type) { + case LINE -> new Line(name, density, group, userDefined); + case SURFACE -> new Surface(name, density, group, userDefined); + case BULK -> new Bulk(name, density, group, userDefined); + case CUSTOM -> new Custom(name, density, group, userDefined); + default -> throw new IllegalArgumentException("Unknown material type: " + type); + }; + } + public static Material newMaterial(Type type, String name, double density, boolean userDefined) { - switch (type) { - case LINE: - return new Material.Line(name, density, userDefined); - - case SURFACE: - return new Material.Surface(name, density, userDefined); - - case BULK: - return new Material.Bulk(name, density, userDefined); - - case CUSTOM: - return new Material.Custom(name, density, userDefined); - - default: - throw new IllegalArgumentException("Unknown material type: " + type); - } + return newMaterial(type, name, density, null, userDefined); } public String toStorableString() { - return getType().name() + "|" + name.replace('|', ' ') + '|' + density; + if (group != null) { + return getType().name() + "|" + name.replace('|', ' ') + '|' + density + '|' + group.getDatabaseString(); + } else { + return getType().name() + "|" + name.replace('|', ' ') + '|' + density; + } } /** * Return a material defined by the provided string. * - * @param str the material storage string, formatted as "{type}|{name}|{density}". + * @param str the material storage string, formatted as "{type}|{name}|{density}|{group}". * @param userDefined whether the created material is user-defined. * @return a new Material object. * @throws IllegalArgumentException if str is invalid or null. @@ -226,13 +268,14 @@ public abstract class Material implements Comparable { if (str == null) throw new IllegalArgumentException("Material string is null"); - String[] split = str.split("\\|", 3); + String[] split = str.split("\\|", 4); if (split.length < 3) throw new IllegalArgumentException("Illegal material string: " + str); - Type type = null; + Type type; String name; double density; + MaterialGroup group = null; try { type = Type.valueOf(split[0]); @@ -247,20 +290,21 @@ public abstract class Material implements Comparable { } catch (NumberFormatException e) { throw new IllegalArgumentException("Illegal material string: " + str, e); } - - switch (type) { - case BULK: - return new Material.Bulk(name, density, userDefined); - - case SURFACE: - return new Material.Surface(name, density, userDefined); - - case LINE: - return new Material.Line(name, density, userDefined); - - default: - throw new IllegalArgumentException("Illegal material string: " + str); + + if (split.length == 4) { + try { + group = MaterialGroup.loadFromDatabaseString(split[3]); + } catch (IllegalArgumentException e) { + log.debug(e.toString()); + } } + + return switch (type) { + case BULK -> new Bulk(name, density, group, userDefined); + case SURFACE -> new Surface(name, density, group, userDefined); + case LINE -> new Line(name, density, group, userDefined); + default -> throw new IllegalArgumentException("Illegal material string: " + str); + }; } } diff --git a/core/src/main/java/info/openrocket/core/material/MaterialGroup.java b/core/src/main/java/info/openrocket/core/material/MaterialGroup.java new file mode 100644 index 000000000..70291d404 --- /dev/null +++ b/core/src/main/java/info/openrocket/core/material/MaterialGroup.java @@ -0,0 +1,99 @@ +package info.openrocket.core.material; + +import info.openrocket.core.l10n.Translator; +import info.openrocket.core.startup.Application; + +/** + * A class for categorizing materials. + */ +public class MaterialGroup implements Comparable { + private static final Translator trans = Application.getTranslator(); + + public static final MaterialGroup METALS = new MaterialGroup(trans.get("MaterialGroup.Metals"), "Metals", 0, false); + public static final MaterialGroup WOODS = new MaterialGroup(trans.get("MaterialGroup.Woods"), "Woods", 10, false); + public static final MaterialGroup PLASTICS = new MaterialGroup(trans.get("MaterialGroup.Plastics"), "Plastics", 20, false); + public static final MaterialGroup FABRICS = new MaterialGroup(trans.get("MaterialGroup.Fabrics"), "Fabrics", 30, false); + public static final MaterialGroup PAPER = new MaterialGroup(trans.get("MaterialGroup.PaperProducts"), "PaperProducts", 40, false); + public static final MaterialGroup FOAMS = new MaterialGroup(trans.get("MaterialGroup.Foams"), "Foams", 50, false); + public static final MaterialGroup COMPOSITES = new MaterialGroup(trans.get("MaterialGroup.Composites"), "Composites", 60, false); + public static final MaterialGroup FIBERS = new MaterialGroup(trans.get("MaterialGroup.Fibers"), "Fibres", 70, false); + public static final MaterialGroup THREADS_LINES = new MaterialGroup(trans.get("MaterialGroup.ThreadsLines"), "ThreadsLines", 80, false); + public static final MaterialGroup OTHER = new MaterialGroup(trans.get("MaterialGroup.Other"), "Other", 90, false); + + public static final MaterialGroup CUSTOM = new MaterialGroup(trans.get("MaterialGroup.Custom"), "Custom", 1000, true); + + public static final MaterialGroup[] ALL_GROUPS = { + METALS, + WOODS, + PLASTICS, + FABRICS, + PAPER, + FOAMS, + COMPOSITES, + FIBERS, + THREADS_LINES, + OTHER, + CUSTOM + }; + + private final String name; + private final String databaseString; + private final int priority; + private final boolean userDefined; + + /** + * Create a new material group. + * @param name the name of the group + * @param dataBaseName the name of the group to be used when saving it in a database + * @param priority the priority of the group (lower number = higher priority) + * @param userDefined whether the group is user-defined + */ + private MaterialGroup(String name, String dataBaseName, int priority, boolean userDefined) { + this.name = name; + this.databaseString = dataBaseName; + this.priority = priority; + this.userDefined = userDefined; + } + + public String getName() { + return name; + } + + public String getDatabaseString() { + return databaseString; + } + + public int getPriority() { + return priority; + } + + public boolean isUserDefined() { + return userDefined; + } + + public static MaterialGroup loadFromDatabaseString(String name) { + for (MaterialGroup group : ALL_GROUPS) { + if (group.getDatabaseString().equals(name)) { + return group; + } + } + throw new IllegalArgumentException("Unknown material group: " + name); + } + + @Override + public String toString() { + return getName(); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MaterialGroup)) + return false; + return this.compareTo((MaterialGroup) o) == 0; + } + + @Override + public int compareTo(MaterialGroup o) { + return this.priority - o.priority; + } +} diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index f4b354c78..6d44a4a1b 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -1126,6 +1126,8 @@ MaterialPanel.lbl.ComponentFinish.ttip.longA1 = The component finish affec MaterialPanel.lbl.ComponentFinish.ttip.longA2 = The value indicated is the average roughness height of the surface. MaterialPanel.but.SetForAll = Set for all MaterialPanel.but.SetForAll.ttip = Set this finish for all components of the rocket. +MaterialPanel.but.AddCustomMaterial = Add custom material +MaterialPanel.MaterialComboBox.placeholder = Enter the material name ! PlacementPanel PlacementPanel.title.Placement = Placement @@ -1807,6 +1809,19 @@ material.tubular_nylon_11_mm_7_16_in = Tubular nylon (11 mm, 7/16 in) material.tubular_nylon_14_mm_9_16_in = Tubular nylon (14 mm, 9/16 in) material.tubular_nylon_25_mm_1_in = Tubular nylon (25 mm, 1 in) +!MaterialGroup +MaterialGroup.Metals = Metals +MaterialGroup.Woods = Woods +MaterialGroup.Plastics = Plastics +MaterialGroup.Fabrics = Fabrics +MaterialGroup.PaperProducts = Paper Products +MaterialGroup.Foams = Foams +MaterialGroup.Composites = Composites +MaterialGroup.Fibers = Fibers +MaterialGroup.ThreadsLines = Threads and Lines +MaterialGroup.Other = Other +MaterialGroup.Custom = Custom + ! ExternalComponent ExternalComponent.Rough = Rough ExternalComponent.Roughunfinished = Rough unfinished diff --git a/swing/src/main/java/info/openrocket/swing/gui/adaptors/MaterialModel.java b/swing/src/main/java/info/openrocket/swing/gui/adaptors/MaterialModel.java index 6cf3e76cb..3464a3967 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/adaptors/MaterialModel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/adaptors/MaterialModel.java @@ -25,10 +25,6 @@ public class MaterialModel extends AbstractListModel implements ComboBoxModel, ComponentChangeListener, DatabaseListener, Invalidatable { private static final long serialVersionUID = 4552478532933113655L; private final ModelInvalidator modelInvalidator; - - - private final Material custom; - private final Component parentUIComponent; @@ -53,23 +49,22 @@ public class MaterialModel extends AbstractListModel implements this.parentUIComponent = parent; this.rocketComponent = component; this.type = type; - this.custom = Material.newMaterial( Material.Type.CUSTOM, trans.get ("Material.CUSTOM"), 1.0, true ); - + switch (type) { - case LINE: - this.database = Databases.LINE_MATERIAL; - break; - - case BULK: - this.database = Databases.BULK_MATERIAL; - break; - - case SURFACE: - this.database = Databases.SURFACE_MATERIAL; - break; - - default: - throw new IllegalArgumentException("Unknown material type:"+type); + case LINE: + this.database = Databases.LINE_MATERIAL; + break; + + case BULK: + this.database = Databases.BULK_MATERIAL; + break; + + case SURFACE: + this.database = Databases.SURFACE_MATERIAL; + break; + + default: + throw new IllegalArgumentException("Unknown material type:"+type); } try { @@ -97,55 +92,53 @@ public class MaterialModel extends AbstractListModel implements return; } - if (item == custom) { - - // Open custom material dialog in the future, after combo box has closed - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - CustomMaterialDialog dialog = new CustomMaterialDialog( - SwingUtilities.getWindowAncestor(parentUIComponent), - (Material) getSelectedItem(), true, - //// Define custom material - trans.get("MaterialModel.title.Defcustmat")); - - dialog.setVisible(true); - - if (!dialog.getOkClicked()) - return; - - Material material = dialog.getMaterial(); - setMethod.invoke(rocketComponent, material); - - if (dialog.isAddSelected()) { - database.add(material); - } - } - }); - - } else if (item instanceof Material) { - + if (item instanceof Material) { setMethod.invoke(rocketComponent, item); - } else { throw new IllegalArgumentException("Illegal item class " + item.getClass() + " item=" + item); } } + public void addCustomMaterial() { + CustomMaterialDialog dialog = new CustomMaterialDialog( + SwingUtilities.getWindowAncestor(parentUIComponent), + (Material) getSelectedItem(), true, + trans.get("MaterialModel.title.Defcustmat")); + + dialog.setVisible(true); + + if (!dialog.getOkClicked()) + return; + + Material material = dialog.getMaterial(); + setMethod.invoke(rocketComponent, material); + + // TODO: add to permanent database if addSelected, add to document database otherwise + if (dialog.isAddSelected()) { + database.add(material); + } + } + @Override public Material getElementAt(int index) { - if (index == database.size()) { - return custom; - } else if (index >= database.size()+1) { + if (index >= database.size()) { return null; } return database.get(index); } + public Material[] getAllMaterials() { + Material[] materials = new Material[database.size()]; + for (int i = 0; i < database.size(); i++) { + materials[i] = database.get(i); + } + return materials; + } + @Override public int getSize() { - return database.size() + 1; + return database.size(); } public Material.Type getType() { @@ -156,7 +149,7 @@ public class MaterialModel extends AbstractListModel implements @Override public void componentChanged(ComponentChangeEvent e) { - if (((ComponentChangeEvent)e).isMassChange()) { + if (e.isMassChange()) { this.fireContentsChanged(this, 0, 0); } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/configdialog/MaterialPanel.java b/swing/src/main/java/info/openrocket/swing/gui/configdialog/MaterialPanel.java index 374f14ba2..436763988 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/configdialog/MaterialPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/configdialog/MaterialPanel.java @@ -1,6 +1,8 @@ package info.openrocket.swing.gui.configdialog; +import info.openrocket.core.material.MaterialGroup; import info.openrocket.core.util.Invalidatable; +import info.openrocket.swing.gui.widgets.SearchableAndCategorizableComboBox; import net.miginfocom.swing.MigLayout; import info.openrocket.core.document.OpenRocketDocument; @@ -19,12 +21,17 @@ import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingUtilities; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * Panel for configuring a component's material and finish properties. @@ -32,6 +39,7 @@ import java.util.List; public class MaterialPanel extends JPanel implements Invalidatable, InvalidatingWidget { private static final Translator trans = Application.getTranslator(); private final List invalidatables = new ArrayList<>(); + private SearchableAndCategorizableComboBox materialCombo = null; public MaterialPanel(RocketComponent component, OpenRocketDocument document, Material.Type type, String materialString, String finishString, @@ -39,24 +47,43 @@ public class MaterialPanel extends JPanel implements Invalidatable, Invalidating super(new MigLayout()); this.setBorder(BorderFactory.createTitledBorder(trans.get("MaterialPanel.title.Material"))); + //// Component material JLabel label = new JLabel(materialString); - //// The component material affects the weight of the component. label.setToolTipText(trans.get("MaterialPanel.lbl.ttip.ComponentMaterialAffects")); this.add(label, "spanx 4, wrap rel"); MaterialModel mm = new MaterialModel(this, component, type, partName); register(mm); - JComboBox materialCombo = new JComboBox<>(mm); - //// The component material affects the weight of the component. - materialCombo.setToolTipText(trans.get("MaterialPanel.combo.ttip.ComponentMaterialAffects")); - this.add(materialCombo, "spanx 4, growx, wrap paragraph"); - order.add(materialCombo); + // Set custom material button + JButton customMaterialButton = new JButton(trans.get("MaterialPanel.but.AddCustomMaterial")); + customMaterialButton.addActionListener(e -> { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + mm.addCustomMaterial(); + if (MaterialPanel.this.materialCombo != null) { + MaterialComboBox.updateComboBoxItems(MaterialPanel.this.materialCombo, MaterialGroup.ALL_GROUPS, mm.getAllMaterials()); + MaterialPanel.this.materialCombo.setSelectedItem(mm.getSelectedItem()); + } + } + }); + }); + + // Material selection combo box + this.materialCombo = MaterialComboBox.createComboBox(MaterialGroup.ALL_GROUPS, mm.getAllMaterials(), customMaterialButton); + this.materialCombo.setSelectedItem(mm.getSelectedItem()); + this.materialCombo.setToolTipText(trans.get("MaterialPanel.combo.ttip.ComponentMaterialAffects")); + this.add(this.materialCombo, "spanx 4, growx, wrap paragraph"); + order.add(this.materialCombo); + + // No surface finish for internal components if (!(component instanceof ExternalComponent)) { return; } + + //// Surface finish label = new JLabel(finishString); - ////The component finish affects the aerodynamic drag of the component.
String tip = trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA1") //// The value indicated is the average roughness height of the surface. + trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA2"); @@ -122,4 +149,63 @@ public class MaterialPanel extends JPanel implements Invalidatable, Invalidating i.invalidateMe(); } } + + public static class MaterialComboBox extends JComboBox { + private static final Translator trans = Application.getTranslator(); + + public static SearchableAndCategorizableComboBox createComboBox( + MaterialGroup[] allGroups, Material[] materials, Component... extraCategoryWidgets) { + final Map materialGroupMap = createMaterialGroupMap(allGroups, materials); + return new SearchableAndCategorizableComboBox<>(materialGroupMap, trans.get("MaterialPanel.MaterialComboBox.placeholder"), extraCategoryWidgets); + } + + public static void updateComboBoxItems(SearchableAndCategorizableComboBox comboBox, + MaterialGroup[] allGroups, Material[] materials) { + final Map materialGroupMap = createMaterialGroupMap(allGroups, materials); + comboBox.updateItems(materialGroupMap); + comboBox.invalidate(); + comboBox.repaint(); + } + + /** + * Create a map of material group and corresponding material. + * @param groups the groups + * @param materials the materials + * @return the map linking the materials to their groups + */ + private static Map createMaterialGroupMap( + MaterialGroup[] groups, Material[] materials) { + // Sort the groups based on priority (lower number = higher priority) + MaterialGroup[] sortedGroups = groups.clone(); + Arrays.sort(sortedGroups, Comparator.comparingInt(MaterialGroup::getPriority)); + + Map map = new LinkedHashMap<>(); + MaterialGroup materialGroup; + for (MaterialGroup group : sortedGroups) { + List itemsForGroup = new ArrayList<>(); + for (Material material : materials) { + materialGroup = material.getGroup(); + if (materialGroup == null) { + if (material.isUserDefined()) { + materialGroup = MaterialGroup.CUSTOM; + } else { + materialGroup = MaterialGroup.OTHER; + } + } + if (materialGroup.equals(group)) { + itemsForGroup.add(material); + } + } + // Sort the types within each group based on priority + itemsForGroup.sort(Comparator.comparingInt(Material::getGroupPriority)); + + map.put(group, itemsForGroup.toArray(new Material[0])); + } + + // Remove empty groups + map.entrySet().removeIf(entry -> entry.getValue().length == 0); + + return map; + } + } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/widgets/SearchableAndCategorizableComboBox.java b/swing/src/main/java/info/openrocket/swing/gui/widgets/SearchableAndCategorizableComboBox.java index d06ac7f49..c7c31211d 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/widgets/SearchableAndCategorizableComboBox.java +++ b/swing/src/main/java/info/openrocket/swing/gui/widgets/SearchableAndCategorizableComboBox.java @@ -22,6 +22,8 @@ import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.Point; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; @@ -43,17 +45,21 @@ import java.util.TreeSet; * If no text is entered, the combobox items are displayed in a categorized popup menu, grouped according to their groups. * @param The type of the group * @param The type of the items + * + * @author Sibo Van Gool */ public class SearchableAndCategorizableComboBox extends JComboBox { - private final JPopupMenu categoryPopup; - private final JPopupMenu searchPopup; - private final PlaceholderTextField searchFieldCategory; - private final PlaceholderTextField searchFieldSearch; - private final JList filteredList; + private final String placeHolderText; + private JPopupMenu categoryPopup; + private JPopupMenu searchPopup; + private PlaceholderTextField searchFieldCategory; + private PlaceholderTextField searchFieldSearch; + private final Component[] extraCategoryWidgets; + private JList filteredList; - private final T[] allItems; - private final Map itemGroupMap; + private T[] allItems; + private Map itemGroupMap; private int highlightedListIdx = -1; @@ -67,29 +73,17 @@ public class SearchableAndCategorizableComboBox extends JComboBox { * Create a searchable and categorizable combo box. * @param itemGroupMap the map of items and their corresponding groups * @param placeHolderText the placeholder text for the search field (when no text is entered) + * @param extraCategoryWidgets extra widgets to add to the category popup. Each widget will be added as a separate menu item. */ - public SearchableAndCategorizableComboBox(Map itemGroupMap, String placeHolderText) { + public SearchableAndCategorizableComboBox(Map itemGroupMap, String placeHolderText, Component... extraCategoryWidgets) { super(); setEditable(false); - this.itemGroupMap = itemGroupMap; - this.allItems = extractItemsFromMap(itemGroupMap); - setModel(new DefaultComboBoxModel<>(allItems)); - initColors(); - // Create the search field widget - searchFieldCategory = new PlaceholderTextField(); - searchFieldCategory.setPlaceholder(placeHolderText); - searchFieldSearch = new PlaceholderTextField(); - - // Create the filtered list - filteredList = createFilteredList(); - - // Create the different popups - categoryPopup = createCategoryPopup(); - searchPopup = createSearchPopup(); - searchPopup.setPreferredSize(categoryPopup.getPreferredSize()); + this.extraCategoryWidgets = extraCategoryWidgets; + this.placeHolderText = placeHolderText; + updateItems(itemGroupMap); // Add key listener for the search fields searchFieldCategory.addKeyListener(new KeyAdapter() { @@ -98,6 +92,7 @@ public class SearchableAndCategorizableComboBox extends JComboBox { overrideActionKeys(e); } + @Override public void keyTyped(KeyEvent e) { EventQueue.invokeLater(() -> { String text = searchFieldCategory.getText(); @@ -131,6 +126,16 @@ public class SearchableAndCategorizableComboBox extends JComboBox { }); } }); + // Fix a bug where the first character would get selected when the search field gets focus (thus deleting it on + // the next key press) + searchFieldSearch.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + SwingUtilities.invokeLater(() -> { + searchFieldSearch.setCaretPosition(searchFieldSearch.getText().length()); + }); + } + }); // Override the mouse listeners to use our custom popup for (MouseListener mouseListener : getMouseListeners()) { @@ -149,6 +154,25 @@ public class SearchableAndCategorizableComboBox extends JComboBox { textSelectionBackground = GUIUtil.getUITheme().getTextSelectionBackgroundColor(); } + public void updateItems(Map itemGroupMap) { + this.itemGroupMap = itemGroupMap; + this.allItems = extractItemsFromMap(itemGroupMap); + setModel(new DefaultComboBoxModel<>(this.allItems)); + + // Create the search field widget + this.searchFieldCategory = new PlaceholderTextField(); + this.searchFieldCategory.setPlaceholder(this.placeHolderText); + this.searchFieldSearch = new PlaceholderTextField(); + + // Create the filtered list + this.filteredList = createFilteredList(); + + // Create the different popups + this.categoryPopup = createCategoryPopup(); + this.searchPopup = createSearchPopup(); + this.searchPopup.setPreferredSize(this.categoryPopup.getPreferredSize()); + } + private T[] extractItemsFromMap(Map itemGroupMap) { Set uniqueItems = new HashSet<>(); // Use a Set to ensure uniqueness for (E group : itemGroupMap.keySet()) { @@ -183,6 +207,13 @@ public class SearchableAndCategorizableComboBox extends JComboBox { menu.add(groupList); } + // Extra widgets + if (extraCategoryWidgets != null) { + for (Component widget : extraCategoryWidgets) { + menu.add(widget); + } + } + return menu; }