Merge pull request #2413 from SiboVG/issue-2338

[#2338] Organize axis plot types selector in categories + add search function
This commit is contained in:
Joe Pfeiffer 2023-11-26 12:57:15 -07:00 committed by GitHub
commit 5938f08145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 828 additions and 100 deletions

View File

@ -811,6 +811,9 @@ simplotpanel.MarkerStyle.btn.VerticalMarker = Vertical line
simplotpanel.MarkerStyle.btn.Icon = Icon
simplotpanel.MarkerStyle.OnlyInTime = Only available for time domain, other domains only support icon markers
! FlightDataComboBox
FlightDataComboBox.placeholder = Enter the data type
! Component add buttons
compaddbuttons.AxialStage = Stage
compaddbuttons.Bodycompandfinsets = Body Components and Fin Sets
@ -2062,6 +2065,20 @@ FlightDataType.TYPE_LONGITUDE = Longitude
FlightDataType.TYPE_CORIOLIS_ACCELERATION = Coriolis acceleration
FlightDataType.TYPE_GRAVITY = Gravitational acceleration
! FlightDataTypeGroup
FlightDataTypeGroup.GROUP_TIME = Time
FlightDataTypeGroup.GROUP_POSITION_AND_MOTION = Position and Motion
FlightDataTypeGroup.GROUP_ORIENTATION = Orientation
FlightDataTypeGroup.GROUP_MASS_AND_INERTIA = Mass and Inertia
FlightDataTypeGroup.GROUP_STABILITY = Stability
FlightDataTypeGroup.GROUP_THRUST_AND_DRAG = Thrust and Drag
FlightDataTypeGroup.GROUP_COEFFICIENTS = Coefficients
FlightDataTypeGroup.GROUP_ATMOSPHERIC_CONDITIONS = Atmospheric Conditions
FlightDataTypeGroup.GROUP_CHARACTERISTIC_NUMBERS = Characteristic Numbers
FlightDataTypeGroup.GROUP_REFERENCE_VALUES = Reference Values
FlightDataTypeGroup.GROUP_SIMULATION_INFORMATION = Simulation Information
FlightDataTypeGroup.GROUP_CUSTOM = Custom
! PlotConfiguration
PlotConfiguration.Verticalmotion = Vertical motion vs. time
PlotConfiguration.Totalmotion = Total motion vs. time

View File

@ -15,7 +15,7 @@ import net.sf.openrocket.util.StringUtils;
/**
* A class defining a storable simulation variable type. This class defined numerous ready
* types, and allows also creating new types with any name. When retrieving types based on
* a name, you should use {@link #getType(String, UnitGroup)} to return the default unit type,
* a name, you should use {@link #getType(String, String, UnitGroup)} to return the default unit type,
* or a new type if the name does not currently exist.
* <p>
* Each type has a type name (description), a unit group and a priority. The type is identified
@ -38,156 +38,203 @@ public class FlightDataType implements Comparable<FlightDataType> {
//// Time
public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), "t", UnitGroup.UNITS_FLIGHT_TIME, 1);
public static final FlightDataType TYPE_TIME = newType(trans.get("FlightDataType.TYPE_TIME"), "t", UnitGroup.UNITS_FLIGHT_TIME,
FlightDataTypeGroup.TIME, 0);
//// Vertical position and motion
//// Position and motion
//// Altitude
public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "h", UnitGroup.UNITS_DISTANCE, 10);
public static final FlightDataType TYPE_ALTITUDE = newType(trans.get("FlightDataType.TYPE_ALTITUDE"), "h", UnitGroup.UNITS_DISTANCE,
FlightDataTypeGroup.POSITION_AND_MOTION, 0);
//// Vertical velocity
public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY, 11);
//// Vertical acceleration
public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION, 12);
//// Total motion
public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY,
FlightDataTypeGroup.POSITION_AND_MOTION, 1);
//// Total velocity
public static final FlightDataType TYPE_VELOCITY_TOTAL = newType(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), "Vt", UnitGroup.UNITS_VELOCITY, 20);
public static final FlightDataType TYPE_VELOCITY_TOTAL = newType(trans.get("FlightDataType.TYPE_VELOCITY_TOTAL"), "Vt", UnitGroup.UNITS_VELOCITY,
FlightDataTypeGroup.POSITION_AND_MOTION, 2);
//// Vertical acceleration
public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION,
FlightDataTypeGroup.POSITION_AND_MOTION, 3);
//// Total acceleration
public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), "At", UnitGroup.UNITS_ACCELERATION, 21);
public static final FlightDataType TYPE_ACCELERATION_TOTAL = newType(trans.get("FlightDataType.TYPE_ACCELERATION_TOTAL"), "At", UnitGroup.UNITS_ACCELERATION,
FlightDataTypeGroup.POSITION_AND_MOTION, 4);
//// Lateral position and motion
//// Position East of launch
public static final FlightDataType TYPE_POSITION_X = newType(trans.get("FlightDataType.TYPE_POSITION_X"), "Px", UnitGroup.UNITS_DISTANCE, 30);
public static final FlightDataType TYPE_POSITION_X = newType(trans.get("FlightDataType.TYPE_POSITION_X"), "Px", UnitGroup.UNITS_DISTANCE,
FlightDataTypeGroup.POSITION_AND_MOTION, 0);
//// Position North of launch
public static final FlightDataType TYPE_POSITION_Y = newType(trans.get("FlightDataType.TYPE_POSITION_Y"), "Py", UnitGroup.UNITS_DISTANCE, 31);
public static final FlightDataType TYPE_POSITION_Y = newType(trans.get("FlightDataType.TYPE_POSITION_Y"), "Py", UnitGroup.UNITS_DISTANCE,
FlightDataTypeGroup.POSITION_AND_MOTION, 1);
//// Lateral distance
public static final FlightDataType TYPE_POSITION_XY = newType(trans.get("FlightDataType.TYPE_POSITION_XY"), "Pl", UnitGroup.UNITS_DISTANCE, 32);
public static final FlightDataType TYPE_POSITION_XY = newType(trans.get("FlightDataType.TYPE_POSITION_XY"), "Pl", UnitGroup.UNITS_DISTANCE,
FlightDataTypeGroup.POSITION_AND_MOTION, 2);
//// Lateral direction
public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "\u03b8l", UnitGroup.UNITS_ANGLE, 33);
public static final FlightDataType TYPE_POSITION_DIRECTION = newType(trans.get("FlightDataType.TYPE_POSITION_DIRECTION"), "\u03b8l", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.POSITION_AND_MOTION, 3);
//// Lateral velocity
public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), "Vl", UnitGroup.UNITS_VELOCITY, 34);
public static final FlightDataType TYPE_VELOCITY_XY = newType(trans.get("FlightDataType.TYPE_VELOCITY_XY"), "Vl", UnitGroup.UNITS_VELOCITY,
FlightDataTypeGroup.POSITION_AND_MOTION, 4);
//// Lateral acceleration
public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), "Al", UnitGroup.UNITS_ACCELERATION, 35);
public static final FlightDataType TYPE_ACCELERATION_XY = newType(trans.get("FlightDataType.TYPE_ACCELERATION_XY"), "Al", UnitGroup.UNITS_ACCELERATION,
FlightDataTypeGroup.POSITION_AND_MOTION, 5);
//// Latitude
public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "\u03c6", UnitGroup.UNITS_ANGLE, 36);
public static final FlightDataType TYPE_LATITUDE = newType(trans.get("FlightDataType.TYPE_LATITUDE"), "\u03c6", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.POSITION_AND_MOTION, 6);
//// Longitude
public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE, 37);
//// Gravity
public static final FlightDataType TYPE_GRAVITY = newType(trans.get("FlightDataType.TYPE_GRAVITY"), "g", UnitGroup.UNITS_ACCELERATION, 38);
//// Angular motion
public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.POSITION_AND_MOTION, 7);
// Orientation
//// Angle of attack
public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "\u03b1", UnitGroup.UNITS_ANGLE, 40);
public static final FlightDataType TYPE_AOA = newType(trans.get("FlightDataType.TYPE_AOA"), "\u03b1", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.ORIENTATION, 0);
//// Roll rate
public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "d\u03a6", UnitGroup.UNITS_ROLL, 41);
public static final FlightDataType TYPE_ROLL_RATE = newType(trans.get("FlightDataType.TYPE_ROLL_RATE"), "d\u03a6", UnitGroup.UNITS_ROLL,
FlightDataTypeGroup.ORIENTATION, 1);
//// Pitch rate
public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "d\u03b8", UnitGroup.UNITS_ROLL, 42);
public static final FlightDataType TYPE_PITCH_RATE = newType(trans.get("FlightDataType.TYPE_PITCH_RATE"), "d\u03b8", UnitGroup.UNITS_ROLL,
FlightDataTypeGroup.ORIENTATION, 2);
//// Yaw rate
public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "d\u03a8", UnitGroup.UNITS_ROLL, 43);
public static final FlightDataType TYPE_YAW_RATE = newType(trans.get("FlightDataType.TYPE_YAW_RATE"), "d\u03a8", UnitGroup.UNITS_ROLL,
FlightDataTypeGroup.ORIENTATION, 3);
//// Vertical orientation (zenith)
public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "\u0398", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.ORIENTATION, 4);
//// Lateral orientation (azimuth)
public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "\u03a6", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.ORIENTATION, 5);
//// Stability information
// Mass and inertia
//// Mass
public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), "m", UnitGroup.UNITS_MASS, 50);
public static final FlightDataType TYPE_MASS = newType(trans.get("FlightDataType.TYPE_MASS"), "m", UnitGroup.UNITS_MASS,
FlightDataTypeGroup.MASS_AND_INERTIA, 0);
//// Motor mass
public static final FlightDataType TYPE_MOTOR_MASS = newType(trans.get("FlightDataType.TYPE_MOTOR_MASS"), "mp", UnitGroup.UNITS_MASS, 51);
public static final FlightDataType TYPE_MOTOR_MASS = newType(trans.get("FlightDataType.TYPE_MOTOR_MASS"), "mp", UnitGroup.UNITS_MASS,
FlightDataTypeGroup.MASS_AND_INERTIA, 1);
//// Longitudinal moment of inertia
public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), "Il", UnitGroup.UNITS_INERTIA, 52);
public static final FlightDataType TYPE_LONGITUDINAL_INERTIA = newType(trans.get("FlightDataType.TYPE_LONGITUDINAL_INERTIA"), "Il", UnitGroup.UNITS_INERTIA,
FlightDataTypeGroup.MASS_AND_INERTIA, 2);
//// Rotational moment of inertia
public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), "Ir", UnitGroup.UNITS_INERTIA, 53);
public static final FlightDataType TYPE_ROTATIONAL_INERTIA = newType(trans.get("FlightDataType.TYPE_ROTATIONAL_INERTIA"), "Ir", UnitGroup.UNITS_INERTIA,
FlightDataTypeGroup.MASS_AND_INERTIA, 3);
//// Gravity
public static final FlightDataType TYPE_GRAVITY = newType(trans.get("FlightDataType.TYPE_GRAVITY"), "g", UnitGroup.UNITS_ACCELERATION,
FlightDataTypeGroup.MASS_AND_INERTIA, 4);
// Stability
//// CP location
public static final FlightDataType TYPE_CP_LOCATION = newType(trans.get("FlightDataType.TYPE_CP_LOCATION"), "Cp", UnitGroup.UNITS_LENGTH, 54);
public static final FlightDataType TYPE_CP_LOCATION = newType(trans.get("FlightDataType.TYPE_CP_LOCATION"), "Cp", UnitGroup.UNITS_LENGTH,
FlightDataTypeGroup.STABILITY, 0);
//// CG location
public static final FlightDataType TYPE_CG_LOCATION = newType(trans.get("FlightDataType.TYPE_CG_LOCATION"), "Cg", UnitGroup.UNITS_LENGTH, 55);
public static final FlightDataType TYPE_CG_LOCATION = newType(trans.get("FlightDataType.TYPE_CG_LOCATION"), "Cg", UnitGroup.UNITS_LENGTH,
FlightDataTypeGroup.STABILITY, 1);
//// Stability margin calibers
public static final FlightDataType TYPE_STABILITY = newType(trans.get("FlightDataType.TYPE_STABILITY"), "S", UnitGroup.UNITS_COEFFICIENT, 56);
public static final FlightDataType TYPE_STABILITY = newType(trans.get("FlightDataType.TYPE_STABILITY"), "S", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.STABILITY, 2);
//// Characteristic numbers
// Characteristic numbers
//// Mach number
public static final FlightDataType TYPE_MACH_NUMBER = newType(trans.get("FlightDataType.TYPE_MACH_NUMBER"), "M", UnitGroup.UNITS_COEFFICIENT, 60);
public static final FlightDataType TYPE_MACH_NUMBER = newType(trans.get("FlightDataType.TYPE_MACH_NUMBER"), "M", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.CHARACTERISTIC_NUMBERS, 0);
//// Reynolds number
public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), "R", UnitGroup.UNITS_COEFFICIENT, 61);
public static final FlightDataType TYPE_REYNOLDS_NUMBER = newType(trans.get("FlightDataType.TYPE_REYNOLDS_NUMBER"), "R", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.CHARACTERISTIC_NUMBERS, 1);
//// Thrust and drag
// Thrust and drag
//// Thrust
public static final FlightDataType TYPE_THRUST_FORCE = newType(trans.get("FlightDataType.TYPE_THRUST_FORCE"), "Ft", UnitGroup.UNITS_FORCE, 70);
public static final FlightDataType TYPE_THRUST_FORCE = newType(trans.get("FlightDataType.TYPE_THRUST_FORCE"), "Ft", UnitGroup.UNITS_FORCE,
FlightDataTypeGroup.THRUST_AND_DRAG, 0);
//// Thrust-to-weight ratio
public static final FlightDataType TYPE_THRUST_WEIGHT_RATIO = newType(trans.get("FlightDataType.TYPE_THRUST_WEIGHT_RATIO"), "Twr", UnitGroup.UNITS_COEFFICIENT, 71);
public static final FlightDataType TYPE_THRUST_WEIGHT_RATIO = newType(trans.get("FlightDataType.TYPE_THRUST_WEIGHT_RATIO"), "Twr", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 1);
//// Drag force
public static final FlightDataType TYPE_DRAG_FORCE = newType(trans.get("FlightDataType.TYPE_DRAG_FORCE"), "Fd", UnitGroup.UNITS_FORCE, 72);
public static final FlightDataType TYPE_DRAG_FORCE = newType(trans.get("FlightDataType.TYPE_DRAG_FORCE"), "Fd", UnitGroup.UNITS_FORCE,
FlightDataTypeGroup.THRUST_AND_DRAG, 2);
//// Drag coefficient
public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT, 73);
//// Axial drag coefficient
public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT, 74);
//// Component drag coefficients
public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 3);
//// Friction drag coefficient
public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), "Cdf", UnitGroup.UNITS_COEFFICIENT, 80);
public static final FlightDataType TYPE_FRICTION_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_FRICTION_DRAG_COEFF"), "Cdf", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 4);
//// Pressure drag coefficient
public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), "Cdp", UnitGroup.UNITS_COEFFICIENT, 81);
public static final FlightDataType TYPE_PRESSURE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_PRESSURE_DRAG_COEFF"), "Cdp", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 5);
//// Base drag coefficient
public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), "Cdb", UnitGroup.UNITS_COEFFICIENT, 82);
public static final FlightDataType TYPE_BASE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_BASE_DRAG_COEFF"), "Cdb", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 6);
//// Axial drag coefficient
public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.THRUST_AND_DRAG, 7);
//// Other coefficients
// Coefficients
//// Normal force coefficient
public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), "Cn", UnitGroup.UNITS_COEFFICIENT, 90);
public static final FlightDataType TYPE_NORMAL_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_NORMAL_FORCE_COEFF"), "Cn", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 0);
//// Pitch moment coefficient
public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "C\u03b8", UnitGroup.UNITS_COEFFICIENT, 91);
public static final FlightDataType TYPE_PITCH_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_MOMENT_COEFF"), "C\u03b8", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 1);
//// Yaw moment coefficient
public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT, 92);
public static final FlightDataType TYPE_YAW_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_MOMENT_COEFF"), "C\u03c4\u03a8", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 2);
//// Side force coefficient
public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "C\u03c4s", UnitGroup.UNITS_COEFFICIENT, 93);
public static final FlightDataType TYPE_SIDE_FORCE_COEFF = newType(trans.get("FlightDataType.TYPE_SIDE_FORCE_COEFF"), "C\u03c4s", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 3);
//// Roll moment coefficient
public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT, 94);
public static final FlightDataType TYPE_ROLL_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_MOMENT_COEFF"), "C\u03c4\u03a6", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 4);
//// Roll forcing coefficient
public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT, 95);
public static final FlightDataType TYPE_ROLL_FORCING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_FORCING_COEFF"), "Cf\u03a6", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 5);
//// Roll damping coefficient
public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT, 96);
public static final FlightDataType TYPE_ROLL_DAMPING_COEFF = newType(trans.get("FlightDataType.TYPE_ROLL_DAMPING_COEFF"), "C\u03b6\u03a6", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 6);
//// Pitch damping coefficient
public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT, 97);
public static final FlightDataType TYPE_PITCH_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_PITCH_DAMPING_MOMENT_COEFF"), "C\u03b6\u03b8", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 7);
//// Yaw damping coefficient
public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT, 98);
public static final FlightDataType TYPE_YAW_DAMPING_MOMENT_COEFF = newType(trans.get("FlightDataType.TYPE_YAW_DAMPING_MOMENT_COEFF"), "C\u03b6\u03a8", UnitGroup.UNITS_COEFFICIENT,
FlightDataTypeGroup.COEFFICIENTS, 8);
//// Coriolis acceleration
public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), "Ac", UnitGroup.UNITS_ACCELERATION, 99);
//// Reference length + area
// Reference values
//// Reference length
public static final FlightDataType TYPE_REFERENCE_LENGTH = newType(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), "Lr", UnitGroup.UNITS_LENGTH, 100);
public static final FlightDataType TYPE_REFERENCE_LENGTH = newType(trans.get("FlightDataType.TYPE_REFERENCE_LENGTH"), "Lr", UnitGroup.UNITS_LENGTH,
FlightDataTypeGroup.REFERENCE_VALUES, 0);
//// Reference area
public static final FlightDataType TYPE_REFERENCE_AREA = newType(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), "Ar", UnitGroup.UNITS_AREA, 101);
public static final FlightDataType TYPE_REFERENCE_AREA = newType(trans.get("FlightDataType.TYPE_REFERENCE_AREA"), "Ar", UnitGroup.UNITS_AREA,
FlightDataTypeGroup.REFERENCE_VALUES, 1);
//// Orientation
//// Vertical orientation (zenith)
public static final FlightDataType TYPE_ORIENTATION_THETA = newType(trans.get("FlightDataType.TYPE_ORIENTATION_THETA"), "\u0398", UnitGroup.UNITS_ANGLE, 106);
//// Lateral orientation (azimuth)
public static final FlightDataType TYPE_ORIENTATION_PHI = newType(trans.get("FlightDataType.TYPE_ORIENTATION_PHI"), "\u03a6", UnitGroup.UNITS_ANGLE, 107);
//// Atmospheric conditions
// Atmospheric conditions
//// Wind velocity
public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), "Vw", UnitGroup.UNITS_VELOCITY, 110);
public static final FlightDataType TYPE_WIND_VELOCITY = newType(trans.get("FlightDataType.TYPE_WIND_VELOCITY"), "Vw", UnitGroup.UNITS_VELOCITY,
FlightDataTypeGroup.ATMOSPHERIC_CONDITIONS, 0);
//// Air temperature
public static final FlightDataType TYPE_AIR_TEMPERATURE = newType(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), "T", UnitGroup.UNITS_TEMPERATURE, 111);
public static final FlightDataType TYPE_AIR_TEMPERATURE = newType(trans.get("FlightDataType.TYPE_AIR_TEMPERATURE"), "T", UnitGroup.UNITS_TEMPERATURE,
FlightDataTypeGroup.ATMOSPHERIC_CONDITIONS, 1);
//// Air pressure
public static final FlightDataType TYPE_AIR_PRESSURE = newType(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), "P", UnitGroup.UNITS_PRESSURE, 112);
public static final FlightDataType TYPE_AIR_PRESSURE = newType(trans.get("FlightDataType.TYPE_AIR_PRESSURE"), "P", UnitGroup.UNITS_PRESSURE,
FlightDataTypeGroup.ATMOSPHERIC_CONDITIONS, 2);
//// Speed of sound
public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), "Vs", UnitGroup.UNITS_VELOCITY, 113);
public static final FlightDataType TYPE_SPEED_OF_SOUND = newType(trans.get("FlightDataType.TYPE_SPEED_OF_SOUND"), "Vs", UnitGroup.UNITS_VELOCITY,
FlightDataTypeGroup.ATMOSPHERIC_CONDITIONS, 3);
//// Simulation information
// Simulation information
//// Simulation time step
public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), "dt", UnitGroup.UNITS_TIME_STEP, 200);
public static final FlightDataType TYPE_TIME_STEP = newType(trans.get("FlightDataType.TYPE_TIME_STEP"), "dt", UnitGroup.UNITS_TIME_STEP,
FlightDataTypeGroup.SIMULATION_INFORMATION, 0);
//// Computation time
public static final FlightDataType TYPE_COMPUTATION_TIME = newType(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), "tc", UnitGroup.UNITS_SHORT_TIME, 201);
public static final FlightDataType TYPE_COMPUTATION_TIME = newType(trans.get("FlightDataType.TYPE_COMPUTATION_TIME"), "tc", UnitGroup.UNITS_SHORT_TIME,
FlightDataTypeGroup.SIMULATION_INFORMATION, 1);
// An array of all the built in types
public static final FlightDataType[] ALL_TYPES = {
@ -286,7 +333,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
// otherwise, just return what we found
if ( !u.equals(type.getUnitGroup()) )
{
oldPriority = type.priority;
oldPriority = type.groupPriority;
EXISTING_TYPES.remove(type);
log.info("Unitgroup of type "+type.getName() +
", has changed from "+type.getUnitGroup().toString() +
@ -294,7 +341,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
". Removing old version.");
}
else if (!s.equals(type.getName())) {
oldPriority = type.priority;
oldPriority = type.groupPriority;
EXISTING_TYPES.remove(type);
log.info("Name of type "+type.getName()+", has changed to "+s+". Removing old version.");
}
@ -329,9 +376,20 @@ public class FlightDataType implements Comparable<FlightDataType> {
/**
* Used while initializing the class.
* @param s the name of the type.
* @param symbol the mathematical symbol of the type.
* @param u the unit group of the type.
* @param group the group of the type.
* @param priority the priority of the type within the group.
*/
private static synchronized FlightDataType newType(String s, String symbol, UnitGroup u, FlightDataTypeGroup group, int priority) {
FlightDataType type = new FlightDataType(s, symbol, u, group, priority);
//EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
EXISTING_TYPES.put(symbol, type);
return type;
}
private static synchronized FlightDataType newType(String s, String symbol, UnitGroup u, int priority) {
FlightDataType type = new FlightDataType(s, symbol, u, priority);
FlightDataType type = new FlightDataType(s, symbol, u, FlightDataTypeGroup.CUSTOM, priority);
//EXISTING_TYPES.put(s.toLowerCase(Locale.ENGLISH), type);
EXISTING_TYPES.put(symbol, type);
return type;
@ -341,11 +399,12 @@ public class FlightDataType implements Comparable<FlightDataType> {
private final String name;
private final String symbol;
private final UnitGroup units;
private final int priority;
private final FlightDataTypeGroup group;
private final int groupPriority;
private final int hashCode;
private FlightDataType(String typeName, String symbol, UnitGroup units, int priority) {
private FlightDataType(String typeName, String symbol, UnitGroup units, FlightDataTypeGroup group, int priority) {
if (typeName == null)
throw new IllegalArgumentException("typeName is null");
if (units == null)
@ -353,16 +412,11 @@ public class FlightDataType implements Comparable<FlightDataType> {
this.name = typeName;
this.symbol = symbol;
this.units = units;
this.priority = priority;
this.group = group;
this.groupPriority = priority;
this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
}
/*
public void setPriority(int p){
this.priority = p;
}
*/
public String getName() {
return name;
}
@ -374,6 +428,14 @@ public class FlightDataType implements Comparable<FlightDataType> {
public UnitGroup getUnitGroup() {
return units;
}
public FlightDataTypeGroup getGroup() {
return group;
}
public int getGroupPriority() {
return groupPriority;
}
@Override
public String toString() {
@ -394,8 +456,8 @@ public class FlightDataType implements Comparable<FlightDataType> {
@Override
public int compareTo(FlightDataType o) {
if (this.priority != o.priority)
return this.priority - o.priority;
if (this.groupPriority != o.groupPriority)
return this.groupPriority - o.groupPriority;
return this.name.compareToIgnoreCase(o.name);
}
}

View File

@ -0,0 +1,59 @@
package net.sf.openrocket.simulation;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
public class FlightDataTypeGroup {
private static final Translator trans = Application.getTranslator();
public static final FlightDataTypeGroup TIME = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_TIME"), 0);
public static final FlightDataTypeGroup POSITION_AND_MOTION = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_POSITION_AND_MOTION"), 10);
public static final FlightDataTypeGroup ORIENTATION = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_ORIENTATION"), 20);
public static final FlightDataTypeGroup MASS_AND_INERTIA = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_MASS_AND_INERTIA"), 30);
public static final FlightDataTypeGroup STABILITY = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_STABILITY"), 40);
public static final FlightDataTypeGroup THRUST_AND_DRAG = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_THRUST_AND_DRAG"), 50);
public static final FlightDataTypeGroup COEFFICIENTS = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_COEFFICIENTS"), 60);
public static final FlightDataTypeGroup ATMOSPHERIC_CONDITIONS = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_ATMOSPHERIC_CONDITIONS"), 70);
public static final FlightDataTypeGroup CHARACTERISTIC_NUMBERS = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_CHARACTERISTIC_NUMBERS"), 80);
public static final FlightDataTypeGroup REFERENCE_VALUES = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_REFERENCE_VALUES"), 90);
public static final FlightDataTypeGroup SIMULATION_INFORMATION = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_SIMULATION_INFORMATION"), 100);
public static final FlightDataTypeGroup CUSTOM = new FlightDataTypeGroup(trans.get("FlightDataTypeGroup.GROUP_CUSTOM"), 200);
// An array of all the built-in groups
public static final FlightDataTypeGroup[] ALL_GROUPS = {
TIME,
POSITION_AND_MOTION,
ORIENTATION,
MASS_AND_INERTIA,
STABILITY,
THRUST_AND_DRAG,
COEFFICIENTS,
ATMOSPHERIC_CONDITIONS,
CHARACTERISTIC_NUMBERS,
REFERENCE_VALUES,
SIMULATION_INFORMATION,
CUSTOM
};
private final String name;
private final int priority;
private FlightDataTypeGroup(String groupName, int priority) {
this.name = groupName;
this.priority = priority;
}
public String getName() {
return name;
}
public int getPriority() {
return priority;
}
@Override
public String toString() {
return getName();
}
}

View File

@ -0,0 +1,53 @@
package net.sf.openrocket.gui.simulation;
import net.sf.openrocket.gui.widgets.SearchableAndCategorizableComboBox;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightDataTypeGroup;
import net.sf.openrocket.startup.Application;
import javax.swing.JComboBox;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class FlightDataComboBox extends JComboBox<FlightDataType> {
private static final Translator trans = Application.getTranslator();
public static SearchableAndCategorizableComboBox<FlightDataTypeGroup, FlightDataType> createComboBox(FlightDataTypeGroup[] allGroups, FlightDataType[] types) {
final Map<FlightDataTypeGroup, FlightDataType[]> typeGroupMap = createFlightDataGroupMap(allGroups, types);
return new SearchableAndCategorizableComboBox<>(typeGroupMap, trans.get("FlightDataComboBox.placeholder"));
}
/**
* Create a map of flight data group and corresponding flight data types.
* @param groups the groups
* @param types the types
* @return the map linking the types to their groups
*/
private static Map<FlightDataTypeGroup, FlightDataType[]> createFlightDataGroupMap(
FlightDataTypeGroup[] groups, FlightDataType[] types) {
// Sort the groups based on priority (lower number = higher priority)
FlightDataTypeGroup[] sortedGroups = groups.clone();
Arrays.sort(sortedGroups, Comparator.comparingInt(FlightDataTypeGroup::getPriority));
Map<FlightDataTypeGroup, FlightDataType[]> map = new LinkedHashMap<>();
for (FlightDataTypeGroup group : sortedGroups) {
List<FlightDataType> itemsForGroup = new ArrayList<>();
for (FlightDataType type : types) {
if (type.getGroup().equals(group)) {
itemsForGroup.add(type);
}
}
// Sort the types within each group based on priority
itemsForGroup.sort(Comparator.comparingInt(FlightDataType::getGroupPriority));
map.put(group, itemsForGroup.toArray(new FlightDataType[0]));
}
return map;
}
}

View File

@ -37,6 +37,7 @@ import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightDataTypeGroup;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
@ -171,7 +172,7 @@ public class SimulationPlotPanel extends JPanel {
//// X axis type:
this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split");
domainTypeSelector = new JComboBox<FlightDataType>(types);
domainTypeSelector = FlightDataComboBox.createComboBox(FlightDataTypeGroup.ALL_GROUPS, types);
domainTypeSelector.setSelectedItem(configuration.getDomainAxisType());
domainTypeSelector.addItemListener(new ItemListener() {
@Override
@ -487,7 +488,7 @@ public class SimulationPlotPanel extends JPanel {
private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
private final int index;
private JComboBox<FlightDataType> typeSelector;
private final JComboBox<FlightDataType> typeSelector;
private UnitSelector unitSelector;
private JComboBox<String> axisSelector;
@ -497,7 +498,7 @@ public class SimulationPlotPanel extends JPanel {
this.index = plotIndex;
typeSelector = new JComboBox<FlightDataType>(types);
typeSelector = FlightDataComboBox.createComboBox(FlightDataTypeGroup.ALL_GROUPS, types);
typeSelector.setSelectedItem(type);
typeSelector.addItemListener(new ItemListener() {
@Override

View File

@ -0,0 +1,55 @@
package net.sf.openrocket.gui.widgets;
import javax.swing.JTextField;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
/**
* A text field that can display a placeholder when empty.
*
* @author Sibo Van Gool <sibo.vangool@hotmail.com>
*/
public class PlaceholderTextField extends JTextField {
private String placeholder;
public PlaceholderTextField() { }
public PlaceholderTextField(final int pColumns) {
super(pColumns);
}
public PlaceholderTextField(final String pText) {
super(pText);
}
public PlaceholderTextField(final String pText, final int pColumns) {
super(pText, pColumns);
}
public String getPlaceholder() {
return placeholder;
}
@Override
protected void paintComponent(Graphics pG) {
super.paintComponent(pG);
if (placeholder == null || placeholder.isEmpty() || !getText().isEmpty()) {
return;
}
final Graphics2D g = (Graphics2D) pG;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(getDisabledTextColor());
g.drawString(placeholder, getInsets().left, pG.getFontMetrics()
.getMaxAscent() + getInsets().top);
}
public void setPlaceholder(final String s) {
placeholder = s;
}
}

View File

@ -0,0 +1,481 @@
package net.sf.openrocket.gui.widgets;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.UITheme;
import javax.swing.AbstractListModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.basic.BasicArrowButton;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* A combo box that has a search box for searching the items in the combobox.
* 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 <T> The type of the items
*/
public class SearchableAndCategorizableComboBox<E, T> extends JComboBox<T> {
private final JPopupMenu categoryPopup;
private final JPopupMenu searchPopup;
private final PlaceholderTextField searchFieldCategory;
private final PlaceholderTextField searchFieldSearch;
private final JList<T> filteredList;
private final T[] allItems;
private final Map<E, T[]> itemGroupMap;
private int highlightedListIdx = -1;
private static Color textSelectionBackground;
static {
initColors();
}
/**
* 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)
*/
public SearchableAndCategorizableComboBox(Map<E, T[]> itemGroupMap, String placeHolderText) {
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());
// Add key listener for the search fields
searchFieldCategory.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
overrideActionKeys(e);
}
public void keyTyped(KeyEvent e) {
EventQueue.invokeLater(() -> {
String text = searchFieldCategory.getText();
highlightedListIdx = 0; // Start with the first item selected
searchFieldSearch.setText(text);
if (!text.isEmpty() && !searchPopup.isVisible()) {
hideCategoryPopup();
showSearchPopup();
filter(text);
}
});
}
});
searchFieldSearch.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
overrideActionKeys(e);
}
@Override
public void keyTyped(KeyEvent e) {
EventQueue.invokeLater(() -> {
String text = searchFieldSearch.getText();
highlightedListIdx = 0; // Start with the first item selected
searchFieldCategory.setText(text);
if (text.isEmpty() && !categoryPopup.isVisible()) {
hideSearchPopup();
showCategoryPopup();
}
filter(text);
});
}
});
// Override the mouse listeners to use our custom popup
for (MouseListener mouseListener : getMouseListeners()) {
removeMouseListener(mouseListener);
}
addMouseListeners();
}
private static void initColors() {
updateColors();
UITheme.Theme.addUIThemeChangeListener(SearchableAndCategorizableComboBox::updateColors);
}
private static void updateColors() {
textSelectionBackground = GUIUtil.getUITheme().getTextSelectionBackgroundColor();
}
private T[] extractItemsFromMap(Map<E, T[]> itemGroupMap) {
Set<T> uniqueItems = new HashSet<>(); // Use a Set to ensure uniqueness
for (E group : itemGroupMap.keySet()) {
uniqueItems.addAll(Arrays.asList(itemGroupMap.get(group)));
}
ArrayList<T> items = new ArrayList<>(uniqueItems);
return items.toArray((T[]) new Object[0]);
}
private JPopupMenu createCategoryPopup() {
final JPopupMenu menu = new JPopupMenu();
// Add the search field at the top
menu.add(searchFieldCategory);
menu.addSeparator(); // Separator between search field and menu items
// Fill the menu with the groups
for (E group : itemGroupMap.keySet()) {
JMenu groupList = new JMenu(group.toString());
T[] itemsForGroup = itemGroupMap.get(group);
if (itemsForGroup != null) {
for (T item : itemsForGroup) {
JMenuItem itemMenu = new JMenuItem(item.toString());
itemMenu.addActionListener(e -> {
setSelectedItem(item);
});
groupList.add(itemMenu);
}
}
menu.add(groupList);
}
return menu;
}
private JPopupMenu createSearchPopup() {
final JPopupMenu menu = new JPopupMenu();
menu.setLayout(new BorderLayout());
// Add the search field at the top
menu.add(searchFieldSearch, BorderLayout.NORTH);
menu.addSeparator();
menu.add(new JScrollPane(filteredList));
return menu;
}
private JList<T> createFilteredList() {
JList<T> list = new JList<>(); // Don't fill the list with the items yet, this will be done during filtering
list.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
T item = (T) value;
String itemName = item.toString();
if (itemName.toLowerCase().contains(searchFieldSearch.getText().toLowerCase())) {
// Use HTML to underline matching text
itemName = itemName.replaceAll("(?i)(" + searchFieldSearch.getText() + ")", "<u>$1</u>");
label.setText("<html>" + itemName + "</html>");
}
// Set the hover color
if (highlightedListIdx == index || isSelected) {
label.setBackground(textSelectionBackground);
label.setOpaque(true);
} else {
label.setOpaque(false);
}
return label;
}
});
list.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
Point p = new Point(e.getX(),e.getY());
int index = list.locationToIndex(p);
if (index != highlightedListIdx) {
highlightedListIdx = index;
list.repaint();
}
}
});
list.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
// Check if the event is in the final phase of change
if (!e.getValueIsAdjusting()) {
selectComboBoxItemFromFilteredList();
}
}
});
return list;
}
private void selectComboBoxItemFromFilteredList() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
T selectedItem = filteredList.getSelectedValue();
if (selectedItem != null) {
SearchableAndCategorizableComboBox.this.setSelectedItem(selectedItem);
// Hide the popups after selection
hideCategoryPopup();
hideSearchPopup();
}
}
});
}
private void showCategoryPopup() {
categoryPopup.show(this, 0, getHeight());
searchFieldSearch.setText("");
searchFieldCategory.setText("");
}
private void hideCategoryPopup() {
categoryPopup.setVisible(false);
}
private void showSearchPopup() {
searchPopup.show(this, 0, getHeight());
}
private void hideSearchPopup() {
searchPopup.setVisible(false);
}
private void filter(String text) {
filteredList.removeAll();
String searchText = text.toLowerCase();
SortedListModel<T> filteredModel = new SortedListModel<>();
for (T item : this.allItems) {
if (item.toString().toLowerCase().contains(searchText)) {
filteredModel.add(item);
}
}
filteredList.setModel(filteredModel);
filteredList.revalidate();
filteredList.repaint();
}
private Component getArrowButton() {
for (Component child : getComponents()) {
if (child instanceof BasicArrowButton) {
return child;
}
}
return null;
}
@Override
public void showPopup() {
// Override the default JComboBox showPopup() to do nothing
// Our custom popup will be shown by the MouseListener
}
@Override
public boolean isPopupVisible() {
return categoryPopup.isVisible() || searchPopup.isVisible();
}
/**
* Override the default action keys (escape, enter, arrow keys) to do our own actions.
* @param e the key event
*/
private void overrideActionKeys(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
hideCategoryPopup();
hideSearchPopup();
} else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
selectHighlightedItemInFilteredList();
} else if (e.getKeyCode() == KeyEvent.VK_DOWN || e.getKeyCode() == KeyEvent.VK_RIGHT) {
highlightNextItemInFilteredList();
} else if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_LEFT) {
highlightPreviousItemInFilteredList();
}
}
/**
* Select the highlighted item in the filtered list and hide the popups.
*/
private void selectHighlightedItemInFilteredList() {
if (highlightedListIdx >= filteredList.getModel().getSize() || highlightedListIdx < 0 || !searchPopup.isVisible()) {
return;
}
filteredList.setSelectedIndex(highlightedListIdx);
selectComboBoxItemFromFilteredList();
}
/**
* Highlight the next item in the filtered list.
*/
private void highlightNextItemInFilteredList() {
if (highlightedListIdx + 1 >= filteredList.getModel().getSize() || !searchPopup.isVisible()) {
return;
}
highlightedListIdx++;
filteredList.ensureIndexIsVisible(highlightedListIdx);
filteredList.repaint();
}
/**
* Highlight the previous item in the filtered list.
*/
private void highlightPreviousItemInFilteredList() {
if (highlightedListIdx <= 0 || !searchPopup.isVisible()) {
return;
}
highlightedListIdx--;
filteredList.ensureIndexIsVisible(highlightedListIdx);
filteredList.repaint();
}
/**
* Add mouse listener to widgets of the combobox to open our custom popup menu.
*/
private void addMouseListeners() {
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!isPopupVisible()) {
showCategoryPopup();
}
}
});
}
});
Component arrowButton = getArrowButton();
if (arrowButton != null) {
for (MouseListener mouseListener : arrowButton.getMouseListeners()) {
arrowButton.removeMouseListener(mouseListener);
}
arrowButton.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (!isPopupVisible()) {
showCategoryPopup();
}
}
});
}
});
}
}
private static class SortedListModel<T> extends AbstractListModel<T> {
private final SortedSet<T> model;
public SortedListModel() {
Comparator<T> alphabeticalComparator = new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
return o1.toString().compareToIgnoreCase(o2.toString());
}
};
model = new TreeSet<>(alphabeticalComparator);
}
public int getSize() {
return model.size();
}
public T getElementAt(int index) {
return (T) model.toArray()[index];
}
public void add(T element) {
if (model.add(element)) {
fireContentsChanged(this, 0, getSize());
}
}
public void addAll(T[] elements) {
Collection<T> c = Arrays.asList(elements);
model.addAll(c);
fireContentsChanged(this, 0, getSize());
}
public void clear() {
model.clear();
fireContentsChanged(this, 0, getSize());
}
public boolean contains(T element) {
return model.contains(element);
}
public T firstElement() {
return model.first();
}
public Iterator<T> iterator() {
return model.iterator();
}
public T lastElement() {
return model.last();
}
public boolean removeElement(T element) {
boolean removed = model.remove(element);
if (removed) {
fireContentsChanged(this, 0, getSize());
}
return removed;
}
}
}