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.btn.Icon = Icon
simplotpanel.MarkerStyle.OnlyInTime = Only available for time domain, other domains only support icon markers simplotpanel.MarkerStyle.OnlyInTime = Only available for time domain, other domains only support icon markers
! FlightDataComboBox
FlightDataComboBox.placeholder = Enter the data type
! Component add buttons ! Component add buttons
compaddbuttons.AxialStage = Stage compaddbuttons.AxialStage = Stage
compaddbuttons.Bodycompandfinsets = Body Components and Fin Sets compaddbuttons.Bodycompandfinsets = Body Components and Fin Sets
@ -2062,6 +2065,20 @@ FlightDataType.TYPE_LONGITUDE = Longitude
FlightDataType.TYPE_CORIOLIS_ACCELERATION = Coriolis acceleration FlightDataType.TYPE_CORIOLIS_ACCELERATION = Coriolis acceleration
FlightDataType.TYPE_GRAVITY = Gravitational 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
PlotConfiguration.Verticalmotion = Vertical motion vs. time PlotConfiguration.Verticalmotion = Vertical motion vs. time
PlotConfiguration.Totalmotion = Total 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 * 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 * 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. * or a new type if the name does not currently exist.
* <p> * <p>
* Each type has a type name (description), a unit group and a priority. The type is identified * 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 //// 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 //// 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 //// Vertical velocity
public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY, 11); public static final FlightDataType TYPE_VELOCITY_Z = newType(trans.get("FlightDataType.TYPE_VELOCITY_Z"), "Vz", UnitGroup.UNITS_VELOCITY,
//// Vertical acceleration FlightDataTypeGroup.POSITION_AND_MOTION, 1);
public static final FlightDataType TYPE_ACCELERATION_Z = newType(trans.get("FlightDataType.TYPE_ACCELERATION_Z"), "Az", UnitGroup.UNITS_ACCELERATION, 12);
//// Total motion
//// Total velocity //// 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 //// 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 //// Lateral position and motion
//// Position East of launch //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// Longitude
public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE, 37); public static final FlightDataType TYPE_LONGITUDE = newType(trans.get("FlightDataType.TYPE_LONGITUDE"), "\u03bb", UnitGroup.UNITS_ANGLE,
FlightDataTypeGroup.POSITION_AND_MOTION, 7);
//// Gravity
public static final FlightDataType TYPE_GRAVITY = newType(trans.get("FlightDataType.TYPE_GRAVITY"), "g", UnitGroup.UNITS_ACCELERATION, 38);
//// Angular motion // Orientation
//// Angle of attack //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// Drag coefficient
public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT, 73); public static final FlightDataType TYPE_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_DRAG_COEFF"), "Cd", UnitGroup.UNITS_COEFFICIENT,
//// Axial drag coefficient FlightDataTypeGroup.THRUST_AND_DRAG, 3);
public static final FlightDataType TYPE_AXIAL_DRAG_COEFF = newType(trans.get("FlightDataType.TYPE_AXIAL_DRAG_COEFF"), "Cda", UnitGroup.UNITS_COEFFICIENT, 74);
//// Component drag coefficients
//// Friction drag coefficient //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 //// Coriolis acceleration
public static final FlightDataType TYPE_CORIOLIS_ACCELERATION = newType(trans.get("FlightDataType.TYPE_CORIOLIS_ACCELERATION"), "Ac", UnitGroup.UNITS_ACCELERATION, 99); 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 //// 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 //// 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 // Atmospheric conditions
//// 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
//// Wind velocity //// 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 //// 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 //// 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 //// 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 //// 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 //// 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 // An array of all the built in types
public static final FlightDataType[] ALL_TYPES = { public static final FlightDataType[] ALL_TYPES = {
@ -286,7 +333,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
// otherwise, just return what we found // otherwise, just return what we found
if ( !u.equals(type.getUnitGroup()) ) if ( !u.equals(type.getUnitGroup()) )
{ {
oldPriority = type.priority; oldPriority = type.groupPriority;
EXISTING_TYPES.remove(type); EXISTING_TYPES.remove(type);
log.info("Unitgroup of type "+type.getName() + log.info("Unitgroup of type "+type.getName() +
", has changed from "+type.getUnitGroup().toString() + ", has changed from "+type.getUnitGroup().toString() +
@ -294,7 +341,7 @@ public class FlightDataType implements Comparable<FlightDataType> {
". Removing old version."); ". Removing old version.");
} }
else if (!s.equals(type.getName())) { else if (!s.equals(type.getName())) {
oldPriority = type.priority; oldPriority = type.groupPriority;
EXISTING_TYPES.remove(type); EXISTING_TYPES.remove(type);
log.info("Name of type "+type.getName()+", has changed to "+s+". Removing old version."); 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. * 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) { 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(s.toLowerCase(Locale.ENGLISH), type);
EXISTING_TYPES.put(symbol, type); EXISTING_TYPES.put(symbol, type);
return type; return type;
@ -341,11 +399,12 @@ public class FlightDataType implements Comparable<FlightDataType> {
private final String name; private final String name;
private final String symbol; private final String symbol;
private final UnitGroup units; private final UnitGroup units;
private final int priority; private final FlightDataTypeGroup group;
private final int groupPriority;
private final int hashCode; 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) if (typeName == null)
throw new IllegalArgumentException("typeName is null"); throw new IllegalArgumentException("typeName is null");
if (units == null) if (units == null)
@ -353,16 +412,11 @@ public class FlightDataType implements Comparable<FlightDataType> {
this.name = typeName; this.name = typeName;
this.symbol = symbol; this.symbol = symbol;
this.units = units; this.units = units;
this.priority = priority; this.group = group;
this.groupPriority = priority;
this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode(); this.hashCode = this.name.toLowerCase(Locale.ENGLISH).hashCode();
} }
/*
public void setPriority(int p){
this.priority = p;
}
*/
public String getName() { public String getName() {
return name; return name;
} }
@ -375,6 +429,14 @@ public class FlightDataType implements Comparable<FlightDataType> {
return units; return units;
} }
public FlightDataTypeGroup getGroup() {
return group;
}
public int getGroupPriority() {
return groupPriority;
}
@Override @Override
public String toString() { public String toString() {
return name; //+" ("+symbol+") "+units.getDefaultUnit().toString(); return name; //+" ("+symbol+") "+units.getDefaultUnit().toString();
@ -394,8 +456,8 @@ public class FlightDataType implements Comparable<FlightDataType> {
@Override @Override
public int compareTo(FlightDataType o) { public int compareTo(FlightDataType o) {
if (this.priority != o.priority) if (this.groupPriority != o.groupPriority)
return this.priority - o.priority; return this.groupPriority - o.groupPriority;
return this.name.compareToIgnoreCase(o.name); 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.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataBranch; import net.sf.openrocket.simulation.FlightDataBranch;
import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.FlightDataTypeGroup;
import net.sf.openrocket.simulation.FlightEvent; import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.startup.Application; import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences; import net.sf.openrocket.startup.Preferences;
@ -171,7 +172,7 @@ public class SimulationPlotPanel extends JPanel {
//// X axis type: //// X axis type:
this.add(new JLabel(trans.get("simplotpanel.lbl.Xaxistype")), "spanx, split"); 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.setSelectedItem(configuration.getDomainAxisType());
domainTypeSelector.addItemListener(new ItemListener() { domainTypeSelector.addItemListener(new ItemListener() {
@Override @Override
@ -487,7 +488,7 @@ public class SimulationPlotPanel extends JPanel {
private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME }; private final String[] POSITIONS = { AUTO_NAME, LEFT_NAME, RIGHT_NAME };
private final int index; private final int index;
private JComboBox<FlightDataType> typeSelector; private final JComboBox<FlightDataType> typeSelector;
private UnitSelector unitSelector; private UnitSelector unitSelector;
private JComboBox<String> axisSelector; private JComboBox<String> axisSelector;
@ -497,7 +498,7 @@ public class SimulationPlotPanel extends JPanel {
this.index = plotIndex; this.index = plotIndex;
typeSelector = new JComboBox<FlightDataType>(types); typeSelector = FlightDataComboBox.createComboBox(FlightDataTypeGroup.ALL_GROUPS, types);
typeSelector.setSelectedItem(type); typeSelector.setSelectedItem(type);
typeSelector.addItemListener(new ItemListener() { typeSelector.addItemListener(new ItemListener() {
@Override @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;
}
}
}