Use searchable and categorizable combobox for material panel

This commit is contained in:
SiboVG 2024-07-21 11:26:08 +02:00
parent 3f8a118298
commit 4411cb1b35
7 changed files with 481 additions and 208 deletions

View File

@ -1,5 +1,6 @@
package info.openrocket.core.database; package info.openrocket.core.database;
import info.openrocket.core.material.MaterialGroup;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -40,89 +41,89 @@ public class Databases {
static { static {
// Add default materials // Add default materials
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Acrylic", 1190)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Acrylic", 1190, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Aluminum", 2700)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Aluminum", 2700, MaterialGroup.METALS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Balsa", 170)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Balsa", 170, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Basswood", 500)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Basswood", 500, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Birch", 670)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Birch", 670, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Brass", 8600)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Brass", 8600, MaterialGroup.METALS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cardboard", 680)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cardboard", 680, MaterialGroup.PAPER));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Carbon fiber", 1780)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Carbon fiber", 1780, MaterialGroup.COMPOSITES));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cork", 240)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Cork", 240, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Delrin", 1420)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Delrin", 1420, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Depron (XPS)", 40)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Depron (XPS)", 40, MaterialGroup.FOAMS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Fiberglass", 1850)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Fiberglass", 1850, MaterialGroup.COMPOSITES));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Kraft phenolic", 950)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Kraft phenolic", 950, MaterialGroup.COMPOSITES));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Maple", 755)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Maple", 755, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Nylon", 1150)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Nylon", 1150, MaterialGroup.FIBERS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Paper (office)", 820)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Paper (office)", 820, MaterialGroup.PAPER));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Pine", 530)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Pine", 530, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Plywood (birch)", 630)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Plywood (birch)", 630, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polycarbonate (Lexan)", 1200)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polycarbonate (Lexan)", 1200, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polystyrene", 1050)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Polystyrene", 1050, MaterialGroup.FOAMS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PVC", 1390)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PVC", 1390, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Spruce", 450)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Spruce", 450, MaterialGroup.WOODS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Steel", 7850)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Steel", 7850, MaterialGroup.METALS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam (generic EPS)", 20)); 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)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Styrofoam \"Blue foam\" (XPS)", 32, MaterialGroup.FOAMS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Titanium", 4500)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Titanium", 4500, MaterialGroup.METALS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Quantum tubing", 1050)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Quantum tubing", 1050, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Blue tube", 1300)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "Blue tube", 1300, MaterialGroup.COMPOSITES));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PLA - 100% infill", 1250)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PLA - 100% infill", 1250, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PETG - 100% infill", 1250)); BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "PETG - 100% infill", 1250, MaterialGroup.PLASTICS));
BULK_MATERIAL.add(newMaterial(Material.Type.BULK, "ABS - 100% infill", 1050)); 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, "Ripstop nylon", 0.067, MaterialGroup.FABRICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Mylar", 0.021)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Mylar", 0.021, MaterialGroup.PLASTICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (thin)", 0.015)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (thin)", 0.015, MaterialGroup.PLASTICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (heavy)", 0.040)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Polyethylene (heavy)", 0.040, MaterialGroup.PLASTICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Silk", 0.060)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Silk", 0.060, MaterialGroup.FABRICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Paper (office)", 0.080)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Paper (office)", 0.080, MaterialGroup.PAPER));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cellophane", 0.018)); SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cellophane", 0.018, MaterialGroup.PLASTICS));
SURFACE_MATERIAL.add(newMaterial(Material.Type.SURFACE, "Cr\u00eape paper", 0.025)); 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, "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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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)); 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 // Add user-defined materials
@ -159,9 +160,13 @@ public class Databases {
* @param density density * @param density density
* @return a new object with the material data * @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); 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);
} }

View File

@ -5,6 +5,8 @@ import info.openrocket.core.startup.Application;
import info.openrocket.core.unit.Unit; import info.openrocket.core.unit.Unit;
import info.openrocket.core.unit.UnitGroup; import info.openrocket.core.unit.UnitGroup;
import info.openrocket.core.util.MathUtil; 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. * 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<Material> { public abstract class Material implements Comparable<Material> {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private static final Logger log = LoggerFactory.getLogger(Material.class);
public enum Type { public enum Type {
BULK("Databases.materials.types.Bulk", UnitGroup.UNITS_DENSITY_BULK), BULK("Databases.materials.types.Bulk", UnitGroup.UNITS_DENSITY_BULK),
@ -48,6 +50,10 @@ public abstract class Material implements Comparable<Material> {
///// Definitions of different material types ///// ///// Definitions of different material types /////
public static class Line extends Material { 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) { Line(String name, double density, boolean userDefined) {
super(name, density, userDefined); super(name, density, userDefined);
} }
@ -60,6 +66,10 @@ public abstract class Material implements Comparable<Material> {
public static class Surface extends Material { 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) { Surface(String name, double density, boolean userDefined) {
super(name, density, userDefined); super(name, density, userDefined);
} }
@ -76,6 +86,10 @@ public abstract class Material implements Comparable<Material> {
} }
public static class Bulk extends Material { 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) { Bulk(String name, double density, boolean userDefined) {
super(name, density, userDefined); super(name, density, userDefined);
} }
@ -88,6 +102,10 @@ public abstract class Material implements Comparable<Material> {
public static class Custom extends Material { 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) { Custom(String name, double density, boolean userDefined) {
super(name, density, userDefined); super(name, density, userDefined);
} }
@ -103,20 +121,26 @@ public abstract class Material implements Comparable<Material> {
private final String name; private final String name;
private final double density; private final double density;
private final boolean userDefined; private final boolean userDefined;
private final MaterialGroup group;
/** /**
* Constructor for materials. * Constructor for materials.
* *
* @param name ignored when defining system materials. * @param name ignored when defining system materials.
* @param key ignored when defining user materials. * @param density: the density of the material.
* @param density * @param group the material group.
* @param userDefined true if this is a user defined material, false if it is a system material. * @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.name = name;
this.userDefined = userDefined;
this.density = density; 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() { public double getDensity() {
@ -137,6 +161,17 @@ public abstract class Material implements Comparable<Material> {
public abstract Type getType(); public abstract Type getType();
public MaterialGroup getGroup() {
return group;
}
public int getGroupPriority() {
if (group == null) {
return Integer.MAX_VALUE;
}
return group.getPriority();
}
@Override @Override
public String toString() { public String toString() {
return this.getName(this.getType().getUnitGroup().getDefaultUnit()); return this.getName(this.getType().getUnitGroup().getDefaultUnit());
@ -154,7 +189,14 @@ public abstract class Material implements Comparable<Material> {
if (this.getClass() != o.getClass()) if (this.getClass() != o.getClass())
return false; return false;
Material m = (Material) o; 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<Material> {
* @param type the material type * @param type the material type
* @param name the material name * @param name the material name
* @param density the material density * @param density the material density
* @param group the material group
* @param userDefined whether the material is user-defined or not * @param userDefined whether the material is user-defined or not
* @return the new material * @return the new material
*/ */
public static Material newMaterial(Type type, String name, double density, boolean userDefined) { public static Material newMaterial(Type type, String name, double density, MaterialGroup group, boolean userDefined) {
switch (type) { return switch (type) {
case LINE: case LINE -> new Line(name, density, group, userDefined);
return new Material.Line(name, density, userDefined); case SURFACE -> new Surface(name, density, group, userDefined);
case BULK -> new Bulk(name, density, group, userDefined);
case SURFACE: case CUSTOM -> new Custom(name, density, group, userDefined);
return new Material.Surface(name, density, userDefined); default -> throw new IllegalArgumentException("Unknown material type: " + type);
};
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);
} }
public static Material newMaterial(Type type, String name, double density, boolean userDefined) {
return newMaterial(type, name, density, null, userDefined);
} }
public String toStorableString() { public String toStorableString() {
if (group != null) {
return getType().name() + "|" + name.replace('|', ' ') + '|' + density + '|' + group.getDatabaseString();
} else {
return getType().name() + "|" + name.replace('|', ' ') + '|' + density; return getType().name() + "|" + name.replace('|', ' ') + '|' + density;
} }
}
/** /**
* Return a material defined by the provided string. * 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. * @param userDefined whether the created material is user-defined.
* @return a new <code>Material</code> object. * @return a new <code>Material</code> object.
* @throws IllegalArgumentException if <code>str</code> is invalid or null. * @throws IllegalArgumentException if <code>str</code> is invalid or null.
@ -226,13 +268,14 @@ public abstract class Material implements Comparable<Material> {
if (str == null) if (str == null)
throw new IllegalArgumentException("Material string is null"); throw new IllegalArgumentException("Material string is null");
String[] split = str.split("\\|", 3); String[] split = str.split("\\|", 4);
if (split.length < 3) if (split.length < 3)
throw new IllegalArgumentException("Illegal material string: " + str); throw new IllegalArgumentException("Illegal material string: " + str);
Type type = null; Type type;
String name; String name;
double density; double density;
MaterialGroup group = null;
try { try {
type = Type.valueOf(split[0]); type = Type.valueOf(split[0]);
@ -248,19 +291,20 @@ public abstract class Material implements Comparable<Material> {
throw new IllegalArgumentException("Illegal material string: " + str, e); throw new IllegalArgumentException("Illegal material string: " + str, e);
} }
switch (type) { if (split.length == 4) {
case BULK: try {
return new Material.Bulk(name, density, userDefined); group = MaterialGroup.loadFromDatabaseString(split[3]);
} catch (IllegalArgumentException e) {
case SURFACE: log.debug(e.toString());
return new Material.Surface(name, density, userDefined);
case LINE:
return new Material.Line(name, density, userDefined);
default:
throw new IllegalArgumentException("Illegal material string: " + str);
} }
} }
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);
};
}
} }

View File

@ -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<MaterialGroup> {
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;
}
}

View File

@ -1126,6 +1126,8 @@ MaterialPanel.lbl.ComponentFinish.ttip.longA1 = <html>The component finish affec
MaterialPanel.lbl.ComponentFinish.ttip.longA2 = The value indicated is the average roughness height of the surface. 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 = Set for all
MaterialPanel.but.SetForAll.ttip = Set this finish for all components of the rocket. 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
PlacementPanel.title.Placement = Placement 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_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) 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
ExternalComponent.Rough = Rough ExternalComponent.Rough = Rough
ExternalComponent.Roughunfinished = Rough unfinished ExternalComponent.Roughunfinished = Rough unfinished

View File

@ -26,10 +26,6 @@ public class MaterialModel extends AbstractListModel<Material> implements
private static final long serialVersionUID = 4552478532933113655L; private static final long serialVersionUID = 4552478532933113655L;
private final ModelInvalidator modelInvalidator; private final ModelInvalidator modelInvalidator;
private final Material custom;
private final Component parentUIComponent; private final Component parentUIComponent;
private final RocketComponent rocketComponent; private final RocketComponent rocketComponent;
@ -53,7 +49,6 @@ public class MaterialModel extends AbstractListModel<Material> implements
this.parentUIComponent = parent; this.parentUIComponent = parent;
this.rocketComponent = component; this.rocketComponent = component;
this.type = type; this.type = type;
this.custom = Material.newMaterial( Material.Type.CUSTOM, trans.get ("Material.CUSTOM"), 1.0, true );
switch (type) { switch (type) {
case LINE: case LINE:
@ -97,16 +92,18 @@ public class MaterialModel extends AbstractListModel<Material> implements
return; return;
} }
if (item == custom) { if (item instanceof Material) {
setMethod.invoke(rocketComponent, item);
} else {
throw new IllegalArgumentException("Illegal item class " + item.getClass() +
" item=" + item);
}
}
// Open custom material dialog in the future, after combo box has closed public void addCustomMaterial() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
CustomMaterialDialog dialog = new CustomMaterialDialog( CustomMaterialDialog dialog = new CustomMaterialDialog(
SwingUtilities.getWindowAncestor(parentUIComponent), SwingUtilities.getWindowAncestor(parentUIComponent),
(Material) getSelectedItem(), true, (Material) getSelectedItem(), true,
//// Define custom material
trans.get("MaterialModel.title.Defcustmat")); trans.get("MaterialModel.title.Defcustmat"));
dialog.setVisible(true); dialog.setVisible(true);
@ -117,35 +114,31 @@ public class MaterialModel extends AbstractListModel<Material> implements
Material material = dialog.getMaterial(); Material material = dialog.getMaterial();
setMethod.invoke(rocketComponent, material); setMethod.invoke(rocketComponent, material);
// TODO: add to permanent database if addSelected, add to document database otherwise
if (dialog.isAddSelected()) { if (dialog.isAddSelected()) {
database.add(material); database.add(material);
} }
} }
});
} else if (item instanceof Material) {
setMethod.invoke(rocketComponent, item);
} else {
throw new IllegalArgumentException("Illegal item class " + item.getClass() +
" item=" + item);
}
}
@Override @Override
public Material getElementAt(int index) { public Material getElementAt(int index) {
if (index == database.size()) { if (index >= database.size()) {
return custom;
} else if (index >= database.size()+1) {
return null; return null;
} }
return database.get(index); 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 @Override
public int getSize() { public int getSize() {
return database.size() + 1; return database.size();
} }
public Material.Type getType() { public Material.Type getType() {
@ -156,7 +149,7 @@ public class MaterialModel extends AbstractListModel<Material> implements
@Override @Override
public void componentChanged(ComponentChangeEvent e) { public void componentChanged(ComponentChangeEvent e) {
if (((ComponentChangeEvent)e).isMassChange()) { if (e.isMassChange()) {
this.fireContentsChanged(this, 0, 0); this.fireContentsChanged(this, 0, 0);
} }
} }

View File

@ -1,6 +1,8 @@
package info.openrocket.swing.gui.configdialog; package info.openrocket.swing.gui.configdialog;
import info.openrocket.core.material.MaterialGroup;
import info.openrocket.core.util.Invalidatable; import info.openrocket.core.util.Invalidatable;
import info.openrocket.swing.gui.widgets.SearchableAndCategorizableComboBox;
import net.miginfocom.swing.MigLayout; import net.miginfocom.swing.MigLayout;
import info.openrocket.core.document.OpenRocketDocument; import info.openrocket.core.document.OpenRocketDocument;
@ -19,12 +21,17 @@ import javax.swing.JButton;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import java.awt.Component; import java.awt.Component;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Panel for configuring a component's material and finish properties. * 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 { public class MaterialPanel extends JPanel implements Invalidatable, InvalidatingWidget {
private static final Translator trans = Application.getTranslator(); private static final Translator trans = Application.getTranslator();
private final List<Invalidatable> invalidatables = new ArrayList<>(); private final List<Invalidatable> invalidatables = new ArrayList<>();
private SearchableAndCategorizableComboBox<MaterialGroup, Material> materialCombo = null;
public MaterialPanel(RocketComponent component, OpenRocketDocument document, public MaterialPanel(RocketComponent component, OpenRocketDocument document,
Material.Type type, String materialString, String finishString, Material.Type type, String materialString, String finishString,
@ -39,24 +47,43 @@ public class MaterialPanel extends JPanel implements Invalidatable, Invalidating
super(new MigLayout()); super(new MigLayout());
this.setBorder(BorderFactory.createTitledBorder(trans.get("MaterialPanel.title.Material"))); this.setBorder(BorderFactory.createTitledBorder(trans.get("MaterialPanel.title.Material")));
//// Component material
JLabel label = new JLabel(materialString); JLabel label = new JLabel(materialString);
//// The component material affects the weight of the component.
label.setToolTipText(trans.get("MaterialPanel.lbl.ttip.ComponentMaterialAffects")); label.setToolTipText(trans.get("MaterialPanel.lbl.ttip.ComponentMaterialAffects"));
this.add(label, "spanx 4, wrap rel"); this.add(label, "spanx 4, wrap rel");
MaterialModel mm = new MaterialModel(this, component, type, partName); MaterialModel mm = new MaterialModel(this, component, type, partName);
register(mm); register(mm);
JComboBox<Material> 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)) { if (!(component instanceof ExternalComponent)) {
return; return;
} }
//// Surface finish
label = new JLabel(finishString); label = new JLabel(finishString);
////<html>The component finish affects the aerodynamic drag of the component.<br>
String tip = trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA1") String tip = trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA1")
//// The value indicated is the average roughness height of the surface. //// The value indicated is the average roughness height of the surface.
+ trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA2"); + trans.get("MaterialPanel.lbl.ComponentFinish.ttip.longA2");
@ -122,4 +149,63 @@ public class MaterialPanel extends JPanel implements Invalidatable, Invalidating
i.invalidateMe(); i.invalidateMe();
} }
} }
public static class MaterialComboBox extends JComboBox<Material> {
private static final Translator trans = Application.getTranslator();
public static SearchableAndCategorizableComboBox<MaterialGroup, Material> createComboBox(
MaterialGroup[] allGroups, Material[] materials, Component... extraCategoryWidgets) {
final Map<MaterialGroup, Material[]> materialGroupMap = createMaterialGroupMap(allGroups, materials);
return new SearchableAndCategorizableComboBox<>(materialGroupMap, trans.get("MaterialPanel.MaterialComboBox.placeholder"), extraCategoryWidgets);
}
public static void updateComboBoxItems(SearchableAndCategorizableComboBox<MaterialGroup, Material> comboBox,
MaterialGroup[] allGroups, Material[] materials) {
final Map<MaterialGroup, Material[]> 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<MaterialGroup, Material[]> 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<MaterialGroup, Material[]> map = new LinkedHashMap<>();
MaterialGroup materialGroup;
for (MaterialGroup group : sortedGroups) {
List<Material> 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;
}
}
} }

View File

@ -22,6 +22,8 @@ import java.awt.Color;
import java.awt.Component; import java.awt.Component;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Point; import java.awt.Point;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter; import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent; import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter; 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. * If no text is entered, the combobox items are displayed in a categorized popup menu, grouped according to their groups.
* @param <E> The type of the group * @param <E> The type of the group
* @param <T> The type of the items * @param <T> The type of the items
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/ */
public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> { public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
private final JPopupMenu categoryPopup; private final String placeHolderText;
private final JPopupMenu searchPopup; private JPopupMenu categoryPopup;
private final PlaceholderTextField searchFieldCategory; private JPopupMenu searchPopup;
private final PlaceholderTextField searchFieldSearch; private PlaceholderTextField searchFieldCategory;
private final JList<T> filteredList; private PlaceholderTextField searchFieldSearch;
private final Component[] extraCategoryWidgets;
private JList<T> filteredList;
private final T[] allItems; private T[] allItems;
private final Map<E, T[]> itemGroupMap; private Map<E, T[]> itemGroupMap;
private int highlightedListIdx = -1; private int highlightedListIdx = -1;
@ -67,29 +73,17 @@ public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
* Create a searchable and categorizable combo box. * Create a searchable and categorizable combo box.
* @param itemGroupMap the map of items and their corresponding groups * @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 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<E, T[]> itemGroupMap, String placeHolderText) { public SearchableAndCategorizableComboBox(Map<E, T[]> itemGroupMap, String placeHolderText, Component... extraCategoryWidgets) {
super(); super();
setEditable(false); setEditable(false);
this.itemGroupMap = itemGroupMap;
this.allItems = extractItemsFromMap(itemGroupMap);
setModel(new DefaultComboBoxModel<>(allItems));
initColors(); initColors();
// Create the search field widget this.extraCategoryWidgets = extraCategoryWidgets;
searchFieldCategory = new PlaceholderTextField(); this.placeHolderText = placeHolderText;
searchFieldCategory.setPlaceholder(placeHolderText); updateItems(itemGroupMap);
searchFieldSearch = new PlaceholderTextField();
// Create the filtered list
filteredList = createFilteredList();
// Create the different popups
categoryPopup = createCategoryPopup();
searchPopup = createSearchPopup();
searchPopup.setPreferredSize(categoryPopup.getPreferredSize());
// Add key listener for the search fields // Add key listener for the search fields
searchFieldCategory.addKeyListener(new KeyAdapter() { searchFieldCategory.addKeyListener(new KeyAdapter() {
@ -98,6 +92,7 @@ public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
overrideActionKeys(e); overrideActionKeys(e);
} }
@Override
public void keyTyped(KeyEvent e) { public void keyTyped(KeyEvent e) {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
String text = searchFieldCategory.getText(); String text = searchFieldCategory.getText();
@ -131,6 +126,16 @@ public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
}); });
} }
}); });
// 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 // Override the mouse listeners to use our custom popup
for (MouseListener mouseListener : getMouseListeners()) { for (MouseListener mouseListener : getMouseListeners()) {
@ -149,6 +154,25 @@ public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
textSelectionBackground = GUIUtil.getUITheme().getTextSelectionBackgroundColor(); textSelectionBackground = GUIUtil.getUITheme().getTextSelectionBackgroundColor();
} }
public void updateItems(Map<E, T[]> 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<E, T[]> itemGroupMap) { private T[] extractItemsFromMap(Map<E, T[]> itemGroupMap) {
Set<T> uniqueItems = new HashSet<>(); // Use a Set to ensure uniqueness Set<T> uniqueItems = new HashSet<>(); // Use a Set to ensure uniqueness
for (E group : itemGroupMap.keySet()) { for (E group : itemGroupMap.keySet()) {
@ -183,6 +207,13 @@ public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
menu.add(groupList); menu.add(groupList);
} }
// Extra widgets
if (extraCategoryWidgets != null) {
for (Component widget : extraCategoryWidgets) {
menu.add(widget);
}
}
return menu; return menu;
} }