diff --git a/README.md b/README.md
index cbfb71713b..726d5c3504 100644
--- a/README.md
+++ b/README.md
@@ -53,7 +53,7 @@ OpenRocket needs help to become even better. Implementing features, writing docu
- Daniel Williams, pod support, maintainer
- Joe Pfeiffer (maintainer)
- Billy Olsen (maintainer)
-- Sibo Van Gool (3D OBJ export, maintainer)
+- Sibo Van Gool (RASAero file format, 3D OBJ export, dark theme, maintainer)
- Neil Weinstock (tester, icons, forum support)
- H. Craig Miller (tester)
diff --git a/core/resources/l10n/messages.properties b/core/resources/l10n/messages.properties
index c490ad629a..711e7e1197 100644
--- a/core/resources/l10n/messages.properties
+++ b/core/resources/l10n/messages.properties
@@ -16,23 +16,18 @@
debug.currentFile = messages.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Do not ask me again
-RocketActions.lbl.Youcanchangedefop = You can change the default operation in the preferences.
-RocketActions.showConfirmDialog.lbl1 = Delete the selected simulations?
-RocketActions.showConfirmDialog.lbl2 = This operation cannot be undone.
-RocketActions.showConfirmDialog.title = Delete simulations
RocketActions.DelCompAct.Delete = Delete
-RocketActions.DelCompAct.ttip.Delete = Delete the selected component.
+RocketActions.DelCompAct.ttip.Delete = Delete the selected components.
RocketActions.DelSimuAct.Delete = Delete
-RocketActions.DelSimuAct.ttip.Delete = Delete the selected simulation.
+RocketActions.DelSimuAct.ttip.Delete = Delete the selected simulations.
RocketActions.DelAct.Delete = Delete
-RocketActions.DelAct.ttip.Delete = Delete the selected component or simulation.
+RocketActions.DelAct.ttip.Delete = Delete the selected components or simulations.
RocketActions.CutAction.Cut = Cut
-RocketActions.CutAction.ttip.Cut = Cut this component or simulation to the clipboard and delete from this design
+RocketActions.CutAction.ttip.Cut = Cut these components or simulations to the clipboard and delete from this design
RocketActions.CopyAct.Copy = Copy
-RocketActions.CopyAct.ttip.Copy = Copy this component (and subcomponents) to the clipboard.
+RocketActions.CopyAct.ttip.Copy = Copy these components (and subcomponents) to the clipboard.
RocketActions.PasteAct.Paste = Paste
-RocketActions.PasteAct.ttip.Paste = Paste the component or simulation on the clipboard to the design.
+RocketActions.PasteAct.ttip.Paste = Paste the components or simulations on the clipboard to the design.
RocketActions.PasteAct.invalidPosition.msg = Invalid paste position for object '%s', ignoring pasting.
RocketActions.PasteAct.invalidPosition.title = Could not paste
RocketActions.DuplicateAct.Duplicate = Duplicate
@@ -385,15 +380,15 @@ pref.dlg.PrefBooleanSelector2 = Confirm
pref.dlg.Add = Add
pref.dlg.DescriptionArea.Adddirectories = Add directories, RASP motor files (*.eng), RockSim engine files (*.rse) or ZIP archives separated by a semicolon (;) to load external thrust curves. Changes will take effect the next time you start OpenRocket.
-PreferencesDialog.lbl.language = Interface language:
-PreferencesDialog.languages.default = System default
-PreferencesDialog.lbl.languageEffect = The language will change the next time you start OpenRocket.
PreferencesDialog.CancelOperation.title = Discard Preference Changes
PreferencesDialog.CancelOperation.msg.discardChanges = Are you sure you want to discard the preference changes?
generalprefs.lbl.language = Interface language
generalprefs.languages.default = System default
generalprefs.lbl.languageEffect = The language will change the next time you start OpenRocket.
+generalprefs.lbl.UITheme = UI Theme
+generalprefs.lbl.FontSize = UI Font Size
+generalprefs.lbl.themeRestartOR = You must restart OpenRocket for the UI changes to take effect.
generalprefs.ImportWarning.title = Reload OpenRocket
generalprefs.ImportWarning.msg = You may need to restart OpenRocket for some of the changes to take effect.
@@ -407,6 +402,10 @@ PreferencesOptionPanel.checkbox.userDirectories.ttip = If unchecked, user direct
PreferencesOptionPanel.checkbox.windowInfo = Export window information (position, size\u2026)
PreferencesOptionPanel.checkbox.windowInfo.ttip = If unchecked, window information (position, size\u2026) will not be exported.
+! UI Themes
+UITheme.Light = Light (Default)
+UITheme.Dark = Dark
+
! Welcome dialog
welcome.dlg.title = Welcome to OpenRocket
welcome.dlg.lbl.thankYou = Thank you for downloading OpenRocket
@@ -581,12 +580,23 @@ simpanel.but.ttip.editsim = Edit the selected simulation
simpanel.but.ttip.runsimu = Re-run the selected simulations
simpanel.but.ttip.deletesim = Delete the selected simulations
simpanel.pop.edit = Edit
+simpanel.pop.edit.ttip= Edit the selected simulation(s)
+simpanel.pop.cut= Cut
+simpanel.pop.cut.ttip= Copy the selected simulation(s) to the clipboard and delete from this design
+simpanel.pop.copy= Copy
+simpanel.pop.copy.ttip= Copy the selected simulation(s) to the clipboard
+simpanel.pop.paste= Paste
+simpanel.pop.paste.ttip= Paste the simulation(s) on the clipboard to the design.
simpanel.pop.plot = Plot / Export
+simpanel.pop.plot.ttip= Plot or Export the selected simulation(s)
simpanel.pop.run = Run
+simpanel.pop.run.ttip= Run the selected simulation(s)
simpanel.pop.delete = Delete
+simpanel.pop.delete.ttip= Delete the selected simulation(s)
simpanel.pop.duplicate = Duplicate
+simpanel.pop.duplicate.ttip= Duplicate the selected simulation(s)
simpanel.pop.exportSimTableToCSV = Export simulation table as CSV file
-simpanel.pop.exportSelectedSimsToCSV = Export simulations as CSV file
+simpanel.pop.exportSelectedSimsToCSV = Export simulation(s) as CSV file
simpanel.pop.exportToCSV.save.dialog.title = Save as CSV file
simpanel.dlg.no.simulation.table.rows = Simulation table has no entries\u2026 Please run a simulation first.
simpanel.checkbox.donotask = Do not ask me again
@@ -594,6 +604,7 @@ simpanel.lbl.defpref = You can change the default operation in the preferences.
simpanel.dlg.lbl.DeleteSim1 = Delete the selected simulations?
simpanel.dlg.lbl.DeleteSim2 = This operation cannot be undone.
simpanel.dlg.lbl.DeleteSim3 = Delete simulations
+simpanel.col.Status = Status
simpanel.col.Name = Name
simpanel.col.Motors = Motors
simpanel.col.Configuration = Configuration
@@ -615,6 +626,7 @@ simpanel.ttip.notSimulated = Not simulated yet
Click Run simulat
simpanel.ttip.noData = No simulation data available.
simpanel.ttip.noWarnings = No warnings.
simpanel.ttip.warnings = Warnings:
+simpanel.msg.invalidCopySelection = Invalid copy selection
! SimulationRunDialog
SimuRunDlg.title.RunSim = Running simulations\u2026
@@ -1192,6 +1204,7 @@ InnerTubeCfg.tab.ttip.Radialpos = Radial position
InnerTubeCfg.lbl.Selectclustercfg = Select cluster configuration:
InnerTubeCfg.lbl.TubeSep = Tube separation:
InnerTubeCfg.lbl.ttip.TubeSep = The separation of the tubes, 1.0 = touching each other
+InnerTubeCfg.lbl.ttip.TubeSepAbs = The separation of the tubes, 0 = touching each other
InnerTubeCfg.lbl.Rotation = Rotation:
InnerTubeCfg.lbl.ttip.Rotation = Rotation angle of the cluster configuration
InnerTubeCfg.lbl.Rotangle = Rotation angle of the cluster configuration
@@ -1200,6 +1213,10 @@ InnerTubeCfg.lbl.longA1 = Split the cluster into separate components.
InnerTubeCfg.lbl.longA2 = This also duplicates all components attached to this inner tube.
InnerTubeCfg.but.Resetsettings = Reset settings
InnerTubeCfg.but.ttip.Resetsettings = Reset the separation and rotation to the default values
+InnerTubeCfg.radioBut.Relative = Relative
+InnerTubeCfg.radioBut.Relative.ttip = The separation is measured relative to the outer diameter of the inner tube
+InnerTubeCfg.radioBut.Absolute = Absolute
+InnerTubeCfg.radioBut.Absolute.ttip = The separation is measured in length units
! LaunchLugConfig
LaunchLugCfg.lbl.Length = Length:
@@ -2025,6 +2042,7 @@ Warning.TUBE_SEPARATION = Space between tube fins may not simulate accurately.
Warning.TUBE_OVERLAP = Overlapping tube fins may not simulate accurately.
Warning.EMPTY_BRANCH = Simulation branch contains no data
Warning.SEPARATION_ORDER = Stages separated in an unreasonable order
+Warning.EARLY_SEPARATION = Stages separated before clearing launch rod/rail
! Scale dialog
ScaleDialog.lbl.scaleRocket = Entire rocket
@@ -2319,7 +2337,7 @@ ComponentPresetChooserDialog.menu.units = Units
ComponentPresetChooserDialog.checkbox.showLegacyCheckBox = Show Legacy Database
ComponentPresetChooserDialog.lbl.favorites = Check to add preset to the preset drop-down menu in the component edit dialog
Directly apply a preset by double-clicking it or by selecting it and closing this window.
ComponentPresetChooserDialog.checkbox.alwaysOpenPreset = Always open this dialog when creating a new %s
-table.column.Favorite = Favorite
+table.column.Favorite = \u2026 Favorite
table.column.Legacy = Legacy
table.column.Manufacturer = Manufacturer
table.column.PartNo = Part Number
diff --git a/core/resources/l10n/messages_ar.properties b/core/resources/l10n/messages_ar.properties
index 96b45e6ab8..5a957d4ccf 100644
--- a/core/resources/l10n/messages_ar.properties
+++ b/core/resources/l10n/messages_ar.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_ar.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = لا تسألني مجددا
-RocketActions.lbl.Youcanchangedefop = يمكنك تغيير العملية الافتراضية في التفضيلات.
-RocketActions.showConfirmDialog.lbl1 = إحذف المحاكاة المختارة؟
-RocketActions.showConfirmDialog.lbl2 = لا يمكنك التراجع عن هذه العملية.
-RocketActions.showConfirmDialog.title = إحذف كل المحاكاة
RocketActions.DelCompAct.Delete = إحذف
RocketActions.DelCompAct.ttip.Delete = إحذف القطعة المختارة
RocketActions.DelSimuAct.Delete = إحذف
@@ -348,9 +343,6 @@ pref.dlg.PrefBooleanSelector2 = تأكيد
pref.dlg.Add = أضف
pref.dlg.DescriptionArea.Adddirectories =.ملفات محرك روكسيم أو أرشيفات زيب مفصولة بفاصلة منقوطة لتحميل منحنيات الدفع الخارجية. ستدخل التغييرات حيز التنفيذ في المرة التالية التي تفتح فيها أوبنروكت (* .rse)أو RASP ملفات محرك (* .eng), أضف الدلائل
-PreferencesDialog.lbl.language = :لغة الواجهة
-PreferencesDialog.languages.default = النظام الافتراضي
-PreferencesDialog.lbl.languageEffect = .ستتغير اللغة في المرة التالية التي تعيد تشغيل أوبنروكت
generalprefs.lbl.language = :لغة الواجهة
generalprefs.languages.default = النظام الافتراضي
generalprefs.lbl.languageEffect = .ستتغير اللغة في المرة التالية التي تعيد تشغيل أوبنروكت
diff --git a/core/resources/l10n/messages_cs.properties b/core/resources/l10n/messages_cs.properties
index 08a09dffd0..a1b326463c 100644
--- a/core/resources/l10n/messages_cs.properties
+++ b/core/resources/l10n/messages_cs.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_cs.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Pr\u0161te se me neptejte
-RocketActions.lbl.Youcanchangedefop = Mu\u017Eete zmenit vchoz operaci v nastaven.
-RocketActions.showConfirmDialog.lbl1 = Chcete smazat oznacenou simulaci?
-RocketActions.showConfirmDialog.lbl2 = Tuto operaci nelze vzt zpet.
-RocketActions.showConfirmDialog.title = Sma\u017E simulace
RocketActions.DelCompAct.Delete = Sma\u017E
RocketActions.DelCompAct.ttip.Delete = Sma\u017E oznacenou komponentu.
RocketActions.DelSimuAct.Delete = Sma\u017E
@@ -269,9 +264,9 @@ pref.dlg.PrefBooleanSelector2 = Potvrd
pref.dlg.Add = Pridej
pref.dlg.DescriptionArea.Adddirectories = Pridej adresre, soubory RASP motor (*.eng), RockSim engine soubory (*.rse) nebo ZIP archiv oddelen oddelovacem (;) k nahrn externch vkonovch prubehu. Zmeny se projev po restaru programu OpenRocket.
-PreferencesDialog.lbl.language = Jazyk rohrann:
-PreferencesDialog.languages.default = Vchoz
-PreferencesDialog.lbl.languageEffect = Jazyk se zmen pri dal\u0161m spu\u0161ten programu OpenRocket.
+generalprefs.lbl.language = Jazyk rohrann:
+generalprefs.languages.default = Vchoz
+generalprefs.lbl.languageEffect = Jazyk se zmen pri dal\u0161m spu\u0161ten programu OpenRocket.
! Software update checker
update.dlg.error.title = Nemohu zskat informace o aktualizacch
diff --git a/core/resources/l10n/messages_de.properties b/core/resources/l10n/messages_de.properties
index 0c68ebee2e..007a46da37 100644
--- a/core/resources/l10n/messages_de.properties
+++ b/core/resources/l10n/messages_de.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_de.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Nicht wieder fragen
-RocketActions.lbl.Youcanchangedefop = Die Standardaktion kann in den Einstellungen gendert werden.
-RocketActions.showConfirmDialog.lbl1 = Simulationen lschen?
-RocketActions.showConfirmDialog.lbl2 = Diese Aktion kann nicht rckgngig gemacht werden.
-RocketActions.showConfirmDialog.title = Simulationen lschen
RocketActions.DelCompAct.Delete = Lschen
RocketActions.DelCompAct.ttip.Delete = Die ausgewhlte Komponente lschen.
RocketActions.DelSimuAct.Delete = Lschen
@@ -271,9 +266,9 @@ pref.dlg.PrefBooleanSelector2 = Best
pref.dlg.Add = Hinzufgen
pref.dlg.DescriptionArea.Adddirectories = Um eigene Schubkurven zu laden, Verzeichnisse, RASP-Motordateien (*.eng), RockSim-Motordateien (*.rse) oder ZIP-Archive mit Semikolon getrennt eingeben. nderungen werden beim nchsten Neustart von OpenRocket bernommen.
-PreferencesDialog.lbl.language = Sprache:
-PreferencesDialog.languages.default = Systemeinstellung
-PreferencesDialog.lbl.languageEffect = Die Sprache wird beim nchsten Neustart von OpenRocket gendert.
+generalprefs.lbl.language = Sprache:
+generalprefs.languages.default = Systemeinstellung
+generalprefs.lbl.languageEffect = Die Sprache wird beim nchsten Neustart von OpenRocket gendert.
! Software update checker
update.dlg.error.title = Es konnten keine Informationen ber Programmaktualisierungen empfangen werden.
diff --git a/core/resources/l10n/messages_es.properties b/core/resources/l10n/messages_es.properties
index 3d989be9bb..db5dc37d17 100644
--- a/core/resources/l10n/messages_es.properties
+++ b/core/resources/l10n/messages_es.properties
@@ -733,9 +733,9 @@ PlotDialog.title.Flightdataplot = Representaci\u00f3n de los datos de vuelo
ComponentTreeRenderer.total = total
-PreferencesDialog.languages.default = Idioma por defecto
-PreferencesDialog.lbl.language = Idioma de la interfaz:
-PreferencesDialog.lbl.languageEffect = El idioma cambiar\u00e1 la pr\u00f3xima vez que abra OpenRocket.
+generalprefs.languages.default = Idioma por defecto
+generalprefs.lbl.language = Idioma de la interfaz:
+generalprefs.lbl.languageEffect = El idioma cambiar\u00e1 la pr\u00f3xima vez que abra OpenRocket.
PresetModel.lbl.custompreset = Personalizado
PresetModel.lbl.partsLib = Biblioteca de piezas
@@ -811,12 +811,6 @@ RocketActions.NewStageAct.Newstage = Nueva etapa
RocketActions.NewStageAct.ttip.Newstage = A\u00f1adir una nueva etapa al dise\u00f1o del cohete
RocketActions.PasteAct.Paste = Pegar
RocketActions.PasteAct.ttip.Paste = Pegar al portapapeles
-! RocketActions
-RocketActions.checkbox.Donotaskmeagain = No volver a preguntarme
-RocketActions.lbl.Youcanchangedefop = Puede modificar la operaci\u00f3n por defecto con sus preferencias
-RocketActions.showConfirmDialog.lbl1 = \u00bfBorrar las simulaciones seleccionadas?
-RocketActions.showConfirmDialog.lbl2 = Esta operaci\u00f3n no puede deshacerse.
-RocketActions.showConfirmDialog.title = Borrar simulaciones
RocketCfg.lbl.Comments = Comentarios:
RocketCfg.lbl.Designer = Dise\u00f1ador:
diff --git a/core/resources/l10n/messages_fr.properties b/core/resources/l10n/messages_fr.properties
index 0eff3be76b..fbbdd38e5d 100644
--- a/core/resources/l10n/messages_fr.properties
+++ b/core/resources/l10n/messages_fr.properties
@@ -725,9 +725,9 @@ PlotDialog.title.Flightdataplot = Trac\u00E9 du vol
ComponentTreeRenderer.total = total
-PreferencesDialog.languages.default = Valeur syst\u00E8me par d\u00E9faut
-PreferencesDialog.lbl.language = Langue du programme:
-PreferencesDialog.lbl.languageEffect = La langue sera chang\u00E9e apr\u00E8s avoir red\u00E9marr\u00E9 OpenRocket.
+generalprefs.languages.default = Valeur syst\u00E8me par d\u00E9faut
+generalprefs.lbl.language = Langue du programme:
+generalprefs.lbl.languageEffect = La langue sera chang\u00E9e apr\u00E8s avoir red\u00E9marr\u00E9 OpenRocket.
PresetModel.lbl.custompreset = Personnalis
PresetModel.lbl.partsLib = Biblioth\u00E8que de pi\u00E8ces
@@ -802,12 +802,6 @@ RocketActions.NewStageAct.Newstage = Nouvel \u00E9tage
RocketActions.NewStageAct.ttip.Newstage = Ajouter un nouvel \u00E9tage au projet.
RocketActions.PasteAct.Paste = Coller
RocketActions.PasteAct.ttip.Paste = Coller la pi\u00E8ce ou simulation pr\u00E9sente dans le presse papier dans le projet.
-! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Ne plus me demander
-RocketActions.lbl.Youcanchangedefop = Vous pouvez changer le mode op\u00E9ratoire par d\u00E9faut dans les pr\u00E9ferences.
-RocketActions.showConfirmDialog.lbl1 = Supprimer les simulations s\u00E9lectionn\u00E9es?
-RocketActions.showConfirmDialog.lbl2 = Cette op\u00E9ration n'est pas r\u00E9versible.
-RocketActions.showConfirmDialog.title = Effacer les simulations
RocketCfg.lbl.Comments = Commentaires:
RocketCfg.lbl.Designer = Concepteur:
diff --git a/core/resources/l10n/messages_it.properties b/core/resources/l10n/messages_it.properties
index 55c33cc870..58d24f634e 100644
--- a/core/resources/l10n/messages_it.properties
+++ b/core/resources/l10n/messages_it.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_it.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Non chiedermelo piu'
-RocketActions.lbl.Youcanchangedefop = Puoi cambiare le operazioni prestabilite in PREFERENZE.
-RocketActions.showConfirmDialog.lbl1 = Cancello le simulazioni selezionate?
-RocketActions.showConfirmDialog.lbl2 = Questa operazione non puo' essere annullata.
-RocketActions.showConfirmDialog.title = Cancello Simulazioni
RocketActions.DelCompAct.Delete = Cancella
RocketActions.DelCompAct.ttip.Delete = Cancello il componente selezionato.
RocketActions.DelSimuAct.Delete = Cancella
@@ -273,9 +268,9 @@ pref.dlg.PrefBooleanSelector2 = Conferma
pref.dlg.Add = Aggiungi
pref.dlg.DescriptionArea.Adddirectories = Aggiungi cartelle, RASP motor files (*.eng), RockSim engine files (*.rse) or ZIP archives separate da puntoevirgola (;) per caricare curve di spinta esterne. I cambiamenti avranno effetto la prossima volta che avvierai OpenRocket.
-PreferencesDialog.lbl.language = Lingua dell'interfaccia:
-PreferencesDialog.languages.default = Predefinita di sistema
-PreferencesDialog.lbl.languageEffect = La lingua sara' cambiata la prossima volta che avvierai OpenRocket.
+generalprefs.lbl.language = Lingua dell'interfaccia:
+generalprefs.languages.default = Predefinita di sistema
+generalprefs.lbl.languageEffect = La lingua sara' cambiata la prossima volta che avvierai OpenRocket.
! Software update checker
update.dlg.error.title = Non sono in grado di recuperare informazioni sugli aggiornamenti
diff --git a/core/resources/l10n/messages_ja.properties b/core/resources/l10n/messages_ja.properties
index 947c2022a6..a593c4c626 100644
--- a/core/resources/l10n/messages_ja.properties
+++ b/core/resources/l10n/messages_ja.properties
@@ -12,11 +12,6 @@
debug.currentFile = messages_ja.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = \u6B21\u56DE\u304B\u3089\u8868\u793A\u3057\u306A\u3044
-RocketActions.lbl.Youcanchangedefop = \u8A2D\u5B9A\u306E\u4E2D\u3067\u30C7\u30D5\u30A9\u30EB\u30C8\u306B\u5909\u66F4\u3059\u308B\u3053\u3068\u304C\u3067\u304D\u307E\u3059
-RocketActions.showConfirmDialog.lbl1 = \u9078\u629E\u3057\u305F\u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3\u3092\u6D88\u53BB\u3057\u307E\u3059\u304B\uFF1F
-RocketActions.showConfirmDialog.lbl2 = \u6D88\u53BB\u3057\u305F\u3089\u5FA9\u5143\u3067\u304D\u307E\u305B\u3093
-RocketActions.showConfirmDialog.title = \u30B7\u30DF\u30E5\u30EC\u30FC\u30B7\u30E7\u30F3\u306E\u524A\u9664
RocketActions.DelCompAct.Delete = \u524A\u9664
RocketActions.DelCompAct.ttip.Delete = \u9078\u629E\u3057\u305F\u90E8\u54C1\u306E\u524A\u9664
RocketActions.DelSimuAct.Delete = \u524A\u9664
@@ -270,9 +265,9 @@ pref.dlg.PrefBooleanSelector2 = \u78BA\u8A8D
pref.dlg.Add = \u8FFD\u52A0
pref.dlg.DescriptionArea.Adddirectories = \u30D5\u30A9\u30EB\u30C0, RASP motor files (*.eng), RockSim engine files (*.rse) \u3082\u3057\u304F\u306F ZIP archives \u3092\u30BB\u30DF\u30B3\u30ED\u30F3(;)\u306B\u3088\u3063\u3066\u5206\u3051\u3089\u308C\u305F\u5F62\u3067\u8FFD\u52A0\u306E\u63A8\u529B\u5C65\u6B74\u3068\u3057\u3066\u8FFD\u52A0\u3067\u304D\u307E\u3059\u3002\u3053\u306E\u5909\u66F4\u306FOpenRocket\u306E\u518D\u8D77\u52D5\u6642\u306B\u6709\u52B9\u306B\u306A\u308A\u307E\u3059
-PreferencesDialog.lbl.language = \u8A00\u8A9E\uFF1A
-PreferencesDialog.languages.default = \u30B7\u30B9\u30C6\u30E0\u8A00\u8A9E
-PreferencesDialog.lbl.languageEffect = \u8A00\u8A9E\u306F\u518D\u8D77\u52D5\u6642\u306B\u5909\u66F4\u3055\u308C\u307E\u3059
+generalprefs.lbl.language = \u8A00\u8A9E\uFF1A
+generalprefs.languages.default = \u30B7\u30B9\u30C6\u30E0\u8A00\u8A9E
+generalprefs.lbl.languageEffect = \u8A00\u8A9E\u306F\u518D\u8D77\u52D5\u6642\u306B\u5909\u66F4\u3055\u308C\u307E\u3059
! Software update checker
update.dlg.error.title = \u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u60C5\u5831\u306E\u8AAD\u307F\u51FA\u3057\u304C\u3067\u304D\u307E\u305B\u3093
diff --git a/core/resources/l10n/messages_nl.properties b/core/resources/l10n/messages_nl.properties
index 8fb96b5397..9b9615c49c 100644
--- a/core/resources/l10n/messages_nl.properties
+++ b/core/resources/l10n/messages_nl.properties
@@ -18,11 +18,6 @@
debug.currentFile = messages_nl.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Niet opnieuw vragen
-RocketActions.lbl.Youcanchangedefop = U kan de standaardbewerking veranderen in de voorkeuren.
-RocketActions.showConfirmDialog.lbl1 = Geselecteerde simulatie verwijderen?
-RocketActions.showConfirmDialog.lbl2 = Deze bewerking kan niet ongedaan worden.
-RocketActions.showConfirmDialog.title = Verwijder simulaties
RocketActions.DelCompAct.Delete = Verwijder
RocketActions.DelCompAct.ttip.Delete = Verwijder het geselecteerde onderdeel.
RocketActions.DelSimuAct.Delete = Verwijder
@@ -350,10 +345,6 @@ pref.dlg.PrefBooleanSelector2 = Bevestig
pref.dlg.Add = Voeg toe
pref.dlg.DescriptionArea.Adddirectories = Voeg folders, RASP motorbestanden (*.eng), RockSim motorbestanden (*.rse) of ZIP-archieven gescheiden door een puntkomma (;) om externe stuwkrachtcurves te laden. Wijzigingen zullen van kracht gaan de volgende keer dat u OpenRocket start.
-PreferencesDialog.lbl.language = Interface taal:
-PreferencesDialog.languages.default = Systeemstandaard
-PreferencesDialog.lbl.languageEffect = De taal zal veranderen de volgende keer dat u OpenRocket start.
-
generalprefs.lbl.language = Interface taal
generalprefs.languages.default = Systeemstandaard
generalprefs.lbl.languageEffect = De taal zal veranderen de volgende keer dat u OpenRocket start.
diff --git a/core/resources/l10n/messages_pl.properties b/core/resources/l10n/messages_pl.properties
index 55577b1054..69ba91cdbe 100644
--- a/core/resources/l10n/messages_pl.properties
+++ b/core/resources/l10n/messages_pl.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_pl.properties
! RocketActions
- RocketActions.checkbox.Donotaskmeagain = Nie pytaj ponownie
- RocketActions.lbl.Youcanchangedefop = Domy\u015Bln\u0105 operacj\u0119 mo\u017Cna zmieni\u0107 w ustawieniach.
- RocketActions.showConfirmDialog.lbl1 = Usun\u0105\u0107 zaznaczone symulacje?
- RocketActions.showConfirmDialog.lbl2 = Tej operacji nie mo\u017Cna cofn\u0105\u0107.
- RocketActions.showConfirmDialog.title = Usu\u0144 symulacje
RocketActions.DelCompAct.Delete = Usu\u0144
RocketActions.DelCompAct.ttip.Delete = Usu\u0144 wybran\u0105 cz\u0119\u015B\u0107
RocketActions.DelSimuAct.Delete = Usu\u0144
@@ -270,10 +265,10 @@
pref.dlg.PrefBooleanSelector2 = Potwierd\u017A
pref.dlg.Add = Dodaj
pref.dlg.DescriptionArea.Adddirectories = Dodaj katalogi, pliki silnikowe RASP (*.eng), Pliki silnikowe RockSim (*.rse) albo archiwa ZIP rozdzielone \u015Brednikiem (;) by za\u0142adowa\u0107 zewn\u0119trzne krzywe si\u0142y ci\u0105gu. Zmiany zostan\u0105 wprowadzone przy kolejnym uruchomieniu OpenRocket.
-
- PreferencesDialog.lbl.language = J\u0119zyk programu:
- PreferencesDialog.languages.default = Domy\u015Blny j\u0119zyk systemu
- PreferencesDialog.lbl.languageEffect = Nowy j\u0119zyk zostanie ustawiony przy kolejnym uruchomieniu OpenRocket.
+
+generalprefs.lbl.language = J\u0119zyk programu:
+generalprefs.languages.default = Domy\u015Blny j\u0119zyk systemu
+generalprefs.lbl.languageEffect = Nowy j\u0119zyk zostanie ustawiony przy kolejnym uruchomieniu OpenRocket.
! Software update checker
update.dlg.error.title = Nie mo\u017Cna uzyska\u0107 informacji o aktualizacji
diff --git a/core/resources/l10n/messages_pt.properties b/core/resources/l10n/messages_pt.properties
index b9e7669c58..89feaa3e60 100644
--- a/core/resources/l10n/messages_pt.properties
+++ b/core/resources/l10n/messages_pt.properties
@@ -709,9 +709,9 @@ PlotDialog.lbl.Chart = Clique e arraste para baixo+direita para am
# PlotDialog
PlotDialog.title.Flightdataplot = Plotagem dos dados de voo
-PreferencesDialog.languages.default = Padr\u00e3o do sistema
-PreferencesDialog.lbl.language = Idioma da interface:
-PreferencesDialog.lbl.languageEffect = A linguagem vai mudar na pr\u00f3xima vez que voc\u00ea iniciar o OpenRocket.
+generalprefs.languages.default = Padr\u00e3o do sistema
+generalprefs.lbl.language = Idioma da interface:
+generalprefs.lbl.languageEffect = A linguagem vai mudar na pr\u00f3xima vez que voc\u00ea iniciar o OpenRocket.
PresetModel.lbl.custompreset = Personalizado
PresetModel.lbl.partsLib = Biblioteca de pe\u00e7as
@@ -786,12 +786,6 @@ RocketActions.NewStageAct.Newstage = Novo est\u00e1gio
RocketActions.NewStageAct.ttip.Newstage = Adicionar um novo est\u00e1gio ao projeto do foguete.
RocketActions.PasteAct.Paste = Colar
RocketActions.PasteAct.ttip.Paste = Cole o componente ou simula\u00e7\u00e3o na \u00e1rea da transfer\u00eancia para o projeto.
-# RocketActions
-RocketActions.checkbox.Donotaskmeagain = N\u00e3o me pergunte novamente
-RocketActions.lbl.Youcanchangedefop = Voc\u00ea pode alterar a opera\u00e7\u00e3o padr\u00e3o em Prefer\u00eancias.
-RocketActions.showConfirmDialog.lbl1 = Excluir as simula\u00e7\u00f5es selecionadas?
-RocketActions.showConfirmDialog.lbl2 = Esta opera\u00e7\u00e3o n\u00e3o poder\u00e1 ser desfeita.
-RocketActions.showConfirmDialog.title = Excluir simula\u00e7\u00f5es
RocketCfg.lbl.Comments = Coment\u00e1rios:
RocketCfg.lbl.Designer = Projetista:
diff --git a/core/resources/l10n/messages_ru.properties b/core/resources/l10n/messages_ru.properties
index 57de961809..7da3fd403f 100644
--- a/core/resources/l10n/messages_ru.properties
+++ b/core/resources/l10n/messages_ru.properties
@@ -16,11 +16,6 @@
debug.currentFile = messages_ru.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = \u0411\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C
-RocketActions.lbl.Youcanchangedefop = \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0438\u0437\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445.
-RocketActions.showConfirmDialog.lbl1 = \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0435 \u0440\u0430\u0441\u0447\u0435\u0442\u044B?
-RocketActions.showConfirmDialog.lbl2 = \u042D\u0442\u0443 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044E \u043D\u0435\u043B\u044C\u0437\u044F \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C.
-RocketActions.showConfirmDialog.title = \u0423\u0434\u0430\u043B\u0435\u043D\u0438\u0435 \u0440\u0430\u0441\u0447\u0435\u0442\u043E\u0432
RocketActions.DelCompAct.Delete = \u0423\u0434\u0430\u043B\u0438\u0442\u044C
RocketActions.DelCompAct.ttip.Delete = \u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442.
RocketActions.DelSimuAct.Delete = \u0423\u0434\u0430\u043B\u0438\u0442\u044C
@@ -346,10 +341,6 @@ pref.dlg.PrefBooleanSelector2 = \u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434
pref.dlg.Add = \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C
pref.dlg.DescriptionArea.Adddirectories = \u0414\u043B\u044F \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u0434\u0430\u043D\u043D\u044B\u0445 \u0441\u0432\u043E\u0438\u0445 \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u0435\u0439 \u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u043A\u0430\u0442\u0430\u043B\u043E\u0433\u0438, \u0444\u0430\u0439\u043B\u044B \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u0435\u0439 RASP (*.eng), \u0444\u0430\u0439\u043B\u044B \u0434\u0432\u0438\u0433\u0430\u0442\u0435\u043B\u0435\u0439 RockSim (*.rse) \u0438\u043B\u0438 ZIP-\u0430\u0440\u0445\u0438\u0432\u044B, \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u043D\u044B\u0435 \u0442\u043E\u0447\u043A\u043E\u0439 \u0441 \u0437\u0430\u043F\u044F\u0442\u043E\u0439 (;). \u0418\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F \u0432\u0441\u0442\u0443\u043F\u044F\u0442 \u0432 \u0441\u0438\u043B\u0443 \u043F\u0440\u0438 \u0441\u043B\u0435\u0434\u0443\u044E\u0449\u0435\u043C \u0437\u0430\u043F\u0443\u0441\u043A\u0435 OpenRocket.
-PreferencesDialog.lbl.language = \u042F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430:
-PreferencesDialog.languages.default = \u0421\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0439
-PreferencesDialog.lbl.languageEffect = \u042F\u0437\u044B\u043A \u0441\u043C\u0435\u043D\u0438\u0442\u0441\u044F \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0435 OpenRocket.
-
generalprefs.lbl.language = \u042F\u0437\u044B\u043A \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430
generalprefs.languages.default = \u0421\u0438\u0441\u0442\u0435\u043C\u043D\u044B\u0439
generalprefs.lbl.languageEffect = \u042F\u0437\u044B\u043A \u0441\u043C\u0435\u043D\u0438\u0442\u0441\u044F \u043F\u0440\u0438 \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0435 OpenRocket.
diff --git a/core/resources/l10n/messages_tr.properties b/core/resources/l10n/messages_tr.properties
index 3c9c22979f..f619a70c12 100644
--- a/core/resources/l10n/messages_tr.properties
+++ b/core/resources/l10n/messages_tr.properties
@@ -17,12 +17,6 @@ purposes)
debug.currentFile = messages.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = Yeniden Tekrarlama
-RocketActions.lbl.Youcanchangedefop = Hatal\u0131 \u00c7al\u0131\u015fmay\u0131 De\u011fi\u015ftirebilirsin
-in the preferences.
-RocketActions.showConfirmDialog.lbl1 = Se\u00e7ili sim\u00fclasyonlar\u0131 silmek ister misiniz?
-RocketActions.showConfirmDialog.lbl2 = Bu \u00e7al\u0131\u015fma tamamlanmad\u0131.
-RocketActions.showConfirmDialog.title = Sim\u00fclasyonlar\u0131 Sil
RocketActions.DelCompAct.Delete = Sil
RocketActions.DelCompAct.ttip.Delete = Se\u00e7ili par\u00e7ay\u0131 sil.
RocketActions.DelSimuAct.Delete = Sil
diff --git a/core/resources/l10n/messages_uk_UA.properties b/core/resources/l10n/messages_uk_UA.properties
index 8a7e912d83..08550fd700 100644
--- a/core/resources/l10n/messages_uk_UA.properties
+++ b/core/resources/l10n/messages_uk_UA.properties
@@ -18,11 +18,6 @@
debug.currentFile = messages.properties
! RocketActions
-RocketActions.checkbox.Donotaskmeagain = \u0411\u0456\u043B\u044C\u0448\u0435 \u043D\u0435 \u043F\u0438\u0442\u0430\u0442\u0438
-RocketActions.lbl.Youcanchangedefop = \u0412\u0438 \u043C\u043E\u0436\u0435\u0442\u0435 \u0437\u043C\u0456\u043D\u0438\u0442\u0438 \u0442\u0438\u043F\u043E\u0432\u0443 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u044E \u0432 \u043E\u043F\u0446\u0438\u044F\u0445.
-RocketActions.showConfirmDialog.lbl1 = \u0412\u0434\u0430\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0440\u0430\u043D\u0443 \u0441\u0438\u043C\u0443\u043B\u044F\u0446\u0456\u044E?
-RocketActions.showConfirmDialog.lbl2 = \u0426\u044F \u043E\u043F\u0435\u0440\u0430\u0446\u0456\u044F \u043D\u0435 \u043C\u043E\u0436\u0435 \u0431\u0443\u0442\u0438 \u0432\u0456\u0434\u043C\u0456\u043D\u0435\u043D\u0430.
-RocketActions.showConfirmDialog.title = \u0412\u0434\u0430\u043B\u0438\u0442\u0438 \u0441\u0438\u043C\u0443\u043B\u044F\u0446\u0456\u044E
RocketActions.DelCompAct.Delete = \u0412\u0434\u0430\u043B\u0438\u0442\u0438
RocketActions.DelCompAct.ttip.Delete = \u0412\u0434\u0430\u043B\u0438\u0442\u0438 \u0432\u0438\u0431\u0440\u0430\u043D\u0443 \u0447\u0430\u0441\u0442\u0438\u043D\u0443.
RocketActions.DelSimuAct.Delete = \u0412\u0434\u0430\u043B\u0438\u0442\u0438
@@ -309,9 +304,9 @@ pref.dlg.PrefBooleanSelector2 = Confirm
pref.dlg.Add = Add
pref.dlg.DescriptionArea.Adddirectories = Add directories, RASP motor files (*.eng), RockSim engine files (*.rse) or ZIP archives separated by a semicolon (;) to load external thrust curves. Changes will take effect the next time you start OpenRocket.
-PreferencesDialog.lbl.language = Interface language:
-PreferencesDialog.languages.default = System default
-PreferencesDialog.lbl.languageEffect = The language will change the next time you start OpenRocket.
+generalprefs.lbl.language = Interface language:
+generalprefs.languages.default = System default
+generalprefs.lbl.languageEffect = The language will change the next time you start OpenRocket.
! Software update checker
update.dlg.error.title = Unable to retrieve update information
diff --git a/core/resources/l10n/messages_zh_CN.properties b/core/resources/l10n/messages_zh_CN.properties
index b4bff94ed6..0a6180ad89 100644
--- a/core/resources/l10n/messages_zh_CN.properties
+++ b/core/resources/l10n/messages_zh_CN.properties
@@ -797,9 +797,9 @@ PlotConfiguration.Verticalmotion = \u5782\u76F4\u8FD0\u52A8 vs. \u65F6\u95F
PlotDialog.CheckBox.Showdatapoints = \u663E\u793A\u6570\u636E\u70B9
PlotDialog.lbl.Chart = \u5DE6\u952E\u62D6\u62FD\u79FB\u52A8\u6570\u636E\u533A. \u6EDA\u8F6E\u7F29\u653E. ctrl-\u6EDA\u8F6E\u4EC5\u7F29\u653EX\u8F74. ctrl-\u5DE6\u952E\u62D6\u62FD\u79FB\u52A8\u89C6\u56FE. \u53F3\u952E\u8FC7\u62FD\u8C03\u6574\u663E\u793A\u5927\u5C0F.
-PreferencesDialog.languages.default = \u7CFB\u7EDF\u9ED8\u8BA4
-PreferencesDialog.lbl.language = \u754C\u9762\u8BED\u8A00:
-PreferencesDialog.lbl.languageEffect = \u8BED\u8A00\u8BBE\u7F6E\u5C06\u5728OpenRocket\u91CD\u542F\u540E\u751F\u6548
+generalprefs.languages.default = \u7CFB\u7EDF\u9ED8\u8BA4
+generalprefs.lbl.language = \u754C\u9762\u8BED\u8A00:
+generalprefs.lbl.languageEffect = \u8BED\u8A00\u8BBE\u7F6E\u5C06\u5728OpenRocket\u91CD\u542F\u540E\u751F\u6548
PresetModel.lbl.custompreset = \u5b9a\u5236
PresetModel.lbl.partsLib = \u96f6\u4ef6\u5e93
@@ -875,12 +875,6 @@ RocketActions.NewStageAct.Newstage = \u65B0\u5EFA\u4E00\u7EA7
RocketActions.NewStageAct.ttip.Newstage = \u5728\u8BBE\u8BA1\u4E2D\u65B0\u5EFA\u4E00\u7EA7
RocketActions.PasteAct.Paste = \u7C98\u8D34
RocketActions.PasteAct.ttip.Paste = \u5C06\u526A\u8D34\u677F\u91CC\u7684\u90E8\u4EF6\u6216\u4EFF\u771F\u7C98\u8D34\u5230\u8BE5\u8BBE\u8BA1\u4E2D
-! RocketActions
-RocketActions.checkbox.Donotaskmeagain = \u4E0D\u518D\u63D0\u793A
-RocketActions.lbl.Youcanchangedefop = \u4F60\u53EF\u4EE5\u4FEE\u6539\u7F3A\u7701\u8BBE\u7F6E
-RocketActions.showConfirmDialog.lbl1 = \u5220\u9664\u9009\u5B9A\u4EFF\u771F?
-RocketActions.showConfirmDialog.lbl2 = \u8BE5\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500
-RocketActions.showConfirmDialog.title = \u5220\u9664\u4EFF\u771F
RocketCfg.lbl.Comments = \u6CE8\u91CA:
RocketCfg.lbl.Designer = \u8BBE\u8BA1\u4EBA:
diff --git a/core/resources/pix/componenticons/pods-small.png b/core/resources/pix/componenticons/pods-small.png
index 17371e846f..d000955a20 100644
Binary files a/core/resources/pix/componenticons/pods-small.png and b/core/resources/pix/componenticons/pods-small.png differ
diff --git a/core/resources/pix/icons/cd-override-subcomponent_dark.png b/core/resources/pix/icons/cd-override-subcomponent_dark.png
new file mode 100644
index 0000000000..6ffec0bc4a
Binary files /dev/null and b/core/resources/pix/icons/cd-override-subcomponent_dark.png differ
diff --git a/core/resources/pix/icons/cd-override-subcomponent.png b/core/resources/pix/icons/cd-override-subcomponent_light.png
similarity index 100%
rename from core/resources/pix/icons/cd-override-subcomponent.png
rename to core/resources/pix/icons/cd-override-subcomponent_light.png
diff --git a/core/resources/pix/icons/cd-override_dark.png b/core/resources/pix/icons/cd-override_dark.png
new file mode 100644
index 0000000000..566d47bcfc
Binary files /dev/null and b/core/resources/pix/icons/cd-override_dark.png differ
diff --git a/core/resources/pix/icons/cd-override.png b/core/resources/pix/icons/cd-override_light.png
similarity index 100%
rename from core/resources/pix/icons/cd-override.png
rename to core/resources/pix/icons/cd-override_light.png
diff --git a/core/resources/pix/icons/cg-override-subcomponent_dark.png b/core/resources/pix/icons/cg-override-subcomponent_dark.png
new file mode 100644
index 0000000000..4930915e0e
Binary files /dev/null and b/core/resources/pix/icons/cg-override-subcomponent_dark.png differ
diff --git a/core/resources/pix/icons/cg-override-subcomponent.png b/core/resources/pix/icons/cg-override-subcomponent_light.png
similarity index 100%
rename from core/resources/pix/icons/cg-override-subcomponent.png
rename to core/resources/pix/icons/cg-override-subcomponent_light.png
diff --git a/core/resources/pix/icons/cg-override_dark.png b/core/resources/pix/icons/cg-override_dark.png
new file mode 100644
index 0000000000..1e1bb74f7c
Binary files /dev/null and b/core/resources/pix/icons/cg-override_dark.png differ
diff --git a/core/resources/pix/icons/cg-override.png b/core/resources/pix/icons/cg-override_light.png
similarity index 100%
rename from core/resources/pix/icons/cg-override.png
rename to core/resources/pix/icons/cg-override_light.png
diff --git a/core/resources/pix/icons/mass-override-subcomponent_dark.png b/core/resources/pix/icons/mass-override-subcomponent_dark.png
new file mode 100755
index 0000000000..60d6fa5b86
Binary files /dev/null and b/core/resources/pix/icons/mass-override-subcomponent_dark.png differ
diff --git a/core/resources/pix/icons/mass-override-subcomponent.png b/core/resources/pix/icons/mass-override-subcomponent_light.png
similarity index 100%
rename from core/resources/pix/icons/mass-override-subcomponent.png
rename to core/resources/pix/icons/mass-override-subcomponent_light.png
diff --git a/core/resources/pix/icons/mass-override_dark.png b/core/resources/pix/icons/mass-override_dark.png
new file mode 100755
index 0000000000..3096bf60f1
Binary files /dev/null and b/core/resources/pix/icons/mass-override_dark.png differ
diff --git a/core/resources/pix/icons/mass-override.png b/core/resources/pix/icons/mass-override_light.png
similarity index 100%
rename from core/resources/pix/icons/mass-override.png
rename to core/resources/pix/icons/mass-override_light.png
diff --git a/core/src/net/sf/openrocket/document/OpenRocketDocument.java b/core/src/net/sf/openrocket/document/OpenRocketDocument.java
index c1ea85f24c..47160f751b 100644
--- a/core/src/net/sf/openrocket/document/OpenRocketDocument.java
+++ b/core/src/net/sf/openrocket/document/OpenRocketDocument.java
@@ -383,12 +383,7 @@ public int getSimulationIndex(Simulation simulation) {
* @param simulation the simulation to be added
*/
public void addSimulation(Simulation simulation) {
- simulations.add(simulation);
- FlightConfigurationId simId = simulation.getId();
- if( !rocket.containsFlightConfigurationID( simId )){
- rocket.createFlightConfiguration(simId);
- }
- fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
+ addSimulation(simulation, simulations.size());
}
/**
@@ -399,6 +394,10 @@ public void addSimulation(Simulation simulation) {
*/
public void addSimulation(Simulation simulation, int n) {
simulations.add(n, simulation);
+ FlightConfigurationId simId = simulation.getId();
+ if( !rocket.containsFlightConfigurationID( simId )){
+ rocket.createFlightConfiguration(simId);
+ }
fireDocumentChangeEvent(new SimulationChangeEvent(simulation));
}
diff --git a/core/src/net/sf/openrocket/logging/Warning.java b/core/src/net/sf/openrocket/logging/Warning.java
index 9d1bc04211..23c4bcb90e 100644
--- a/core/src/net/sf/openrocket/logging/Warning.java
+++ b/core/src/net/sf/openrocket/logging/Warning.java
@@ -386,7 +386,11 @@ public boolean replaceBy(Message other) {
public static final Warning TUBE_SEPARATION = new Other(trans.get("Warning.TUBE_SEPARATION"));
public static final Warning TUBE_OVERLAP = new Other(trans.get("Warning.TUBE_OVERLAP"));
+ /** A Warning
that stage separation occurred at other than the last stage */
public static final Warning SEPARATION_ORDER = new Other(trans.get("Warning.SEPARATION_ORDER"));
+ /** A Warning
that stage separation occurred before the rocket cleared the launch rod or rail */
+ public static final Warning EARLY_SEPARATION = new Other(trans.get("Warning.EARLY_SEPARATION"));
+
public static final Warning EMPTY_BRANCH = new Other(trans.get("Warning.EMPTY_BRANCH"));
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
index b4d3ac47e6..249f8dfeb5 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/AxialStage.java
@@ -170,14 +170,14 @@ public String toDebugSeparation() {
* @return the previous stage in the rocket
*/
public AxialStage getUpperStage() {
- if( null == this.parent ) {
+ if (this.parent == null) {
return null;
- }else if(Rocket.class.isAssignableFrom(this.parent.getClass()) ){
- final int thisIndex = getStageNumber();
- if( 0 < thisIndex ){
- return (AxialStage)parent.getChild(thisIndex-1);
+ } else if (Rocket.class.isAssignableFrom(this.parent.getClass())) {
+ final int thisIndex = parent.getChildPosition(this);
+ if (thisIndex > 0) {
+ return (AxialStage) parent.getChild(thisIndex-1);
}
- }else {
+ } else {
return this.parent.getStage();
}
return null;
diff --git a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java
index 1e17bb335a..c42aec1b00 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/InnerTube.java
@@ -170,6 +170,11 @@ public void setInstanceCount( final int newCount ){
" Please set setClusterConfiguration(ClusterConfiguration) instead.",
new UnsupportedOperationException("InnerTube.setInstanceCount(..) on an"+this.getClass().getSimpleName()));
}
+
+ @Override
+ public boolean isAfter(){
+ return false;
+ }
/**
* Get the cluster scaling. A value of 1.0 indicates that the tubes are packed
@@ -177,14 +182,10 @@ public void setInstanceCount( final int newCount ){
* pack inside each other.
*/
public double getClusterScale() {
+ mutex.verify();
return clusterScale;
}
- @Override
- public boolean isAfter(){
- return false;
- }
-
/**
* Set the cluster scaling.
* @see #getClusterScale()
@@ -203,6 +204,23 @@ public void setClusterScale(double scale) {
clusterScale = scale;
fireComponentChangeEvent(new ComponentChangeEvent(this, ComponentChangeEvent.MASS_CHANGE));
}
+
+ /**
+ * Get the cluster scaling as an absolute distance measurement. A value of 0 indicates that the tubes are packed
+ * touching each other, larger values separate the tubes and smaller values pack inside each other.
+ */
+ public double getClusterScaleAbsolute() {
+ return (getClusterScale() - 1) * getOuterRadius() * 2;
+ }
+
+ /**
+ * Set the absolute cluster scaling (in terms of distance).
+ * @see #getClusterScaleAbsolute()
+ */
+ public void setClusterScaleAbsolute(double scale) {
+ double scaleRel = scale / (getOuterRadius() * 2) + 1;
+ setClusterScale(scaleRel);
+ }
@@ -242,27 +260,21 @@ public double getClusterSeparation() {
}
public List getClusterPoints() {
- List list = new ArrayList(getInstanceCount());
+ List list = new ArrayList<>(getInstanceCount());
List points = cluster.getPoints(clusterRotation - getRadialDirection());
double separation = getClusterSeparation();
+ double yOffset = this.radialPosition * Math.cos(this.radialDirection);
+ double zOffset = this.radialPosition * Math.sin(this.radialDirection);
for (int i = 0; i < points.size() / 2; i++) {
- list.add(new Coordinate(0, points.get(2 * i) * separation, points.get(2 * i + 1) * separation));
+ list.add(new Coordinate(0, points.get(2 * i) * separation + yOffset, points.get(2 * i + 1) * separation + zOffset));
}
return list;
}
@Override
public Coordinate[] getInstanceOffsets(){
-
- if ( 1 == getInstanceCount()) {
- double yOffset = this.radialPosition * Math.cos(this.radialDirection);
- double zOffset = this.radialPosition * Math.sin(this.radialDirection);
- return new Coordinate[] { Coordinate.ZERO.add(0.0, yOffset, zOffset) };
- }
-
List points = getClusterPoints();
-
- return points.toArray( new Coordinate[ points.size() ]);
+ return points.toArray(new Coordinate[0]);
}
// @Override
diff --git a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
index cb38838579..9cd011ead6 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/SymmetricComponent.java
@@ -849,12 +849,20 @@ private boolean inline(final RocketComponent candidate) {
// have a radial offset of 0 from their centerline, we are in line.
if ((this.parent instanceof RingInstanceable) &&
- (!MathUtil.equals(this.parent.getRadiusMethod().getRadius(this.parent.parent, this, this.parent.getRadiusOffset()), 0)))
+ (!MathUtil.equals(this.parent.getRadiusMethod().getRadius(
+ this.parent.parent, this, this.parent.getRadiusOffset()), 0)))
return false;
- if ((candidate.parent instanceof RingInstanceable) &&
- (!MathUtil.equals(candidate.parent.getRadiusMethod().getRadius(candidate.parent.parent, candidate, candidate.parent.getRadiusOffset()), 0)))
- return false;
+ if (candidate.parent instanceof RingInstanceable) {
+ // We need to check if the grandparent of the candidate is a body tube and if the outer radius is automatic.
+ // If so, then this would cause an infinite loop when checking the radius of the radiusMethod.
+ if (candidate.parent.parent == this && (this.isAftRadiusAutomatic() || this.isForeRadiusAutomatic())) {
+ return false;
+ } else {
+ return MathUtil.equals(candidate.parent.getRadiusMethod().getRadius(
+ candidate.parent.parent, candidate, candidate.parent.getRadiusOffset()), 0);
+ }
+ }
return true;
}
diff --git a/core/src/net/sf/openrocket/rocketcomponent/Transition.java b/core/src/net/sf/openrocket/rocketcomponent/Transition.java
index a518844bff..bba87bd3af 100644
--- a/core/src/net/sf/openrocket/rocketcomponent/Transition.java
+++ b/core/src/net/sf/openrocket/rocketcomponent/Transition.java
@@ -118,6 +118,8 @@ public void setForeRadius(double radius, boolean doClamping) {
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
+
+ setForeShoulderRadius(getForeShoulderRadius());
}
public void setForeRadius(double radius) {
@@ -207,6 +209,8 @@ public void setAftRadius(double radius, boolean doClamping) {
clearPreset();
fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
+
+ setAftShoulderRadius(getAftShoulderRadius());
}
public void setAftRadius(double radius) {
@@ -398,6 +402,7 @@ public void setForeShoulderRadius(double foreShoulderRadius) {
((Transition) listener).setForeShoulderRadius(foreShoulderRadius);
}
}
+ foreShoulderRadius = Math.min(foreShoulderRadius, getForeRadius());
if (MathUtil.equals(this.foreShoulderRadius, foreShoulderRadius))
return;
@@ -471,6 +476,8 @@ public void setAftShoulderRadius(double aftShoulderRadius) {
}
}
+ aftShoulderRadius = Math.min(aftShoulderRadius, getAftRadius());
+
if (MathUtil.equals(this.aftShoulderRadius, aftShoulderRadius))
return;
this.aftShoulderRadius = aftShoulderRadius;
diff --git a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
index be9fd8bc67..32977c194e 100644
--- a/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
+++ b/core/src/net/sf/openrocket/simulation/BasicEventSimulationEngine.java
@@ -485,6 +485,11 @@ else if (event.getType() == FlightEvent.Type.EJECTION_CHARGE)
currentStatus.getWarnings().add(Warning.SEPARATION_ORDER);
}
+ // If I haven't cleared the rail yet, flag a warning
+ if (!currentStatus.isLaunchRodCleared()) {
+ currentStatus.getWarnings().add(Warning.EARLY_SEPARATION);
+ }
+
// Create a new simulation branch for the booster
SimulationStatus boosterStatus = new SimulationStatus(currentStatus);
diff --git a/core/src/net/sf/openrocket/startup/Preferences.java b/core/src/net/sf/openrocket/startup/Preferences.java
index 02d272f03d..4c4f60ef0d 100644
--- a/core/src/net/sf/openrocket/startup/Preferences.java
+++ b/core/src/net/sf/openrocket/startup/Preferences.java
@@ -118,6 +118,8 @@ public abstract class Preferences implements ChangeSource {
public static final String LAUNCH_USE_ISA = "LaunchUseISA";
public static final String SIMULATION_TIME_STEP = "SimulationTimeStep";
public static final String GEODETIC_COMPUTATION = "GeodeticComputationStrategy";
+
+ public static final String UI_THEME = "UITheme";
private static final AtmosphericModel ISA_ATMOSPHERIC_MODEL = new ExtendedISAModel();
@@ -549,11 +551,6 @@ public void setTimeStep(double timeStep) {
fireChangeEvent();
}
-
- public final float getRocketInfoFontSize() {
- return (float) (11.0 + 3 * Application.getPreferences().getChoice(Preferences.ROCKET_INFO_FONT_SIZE, 2, 0));
- }
-
/**
* Enable/Disable the auto-opening of the last edited design file on startup.
*/
@@ -820,25 +817,6 @@ public final void putEnum(String key, Enum> value) {
}
}
- public Color getDefaultColor(Class extends RocketComponent> c) {
- String color = get("componentColors", c, StaticFieldHolder.DEFAULT_COLORS);
- if (color == null)
- return Color.BLACK;
-
- Color clr = parseColor(color);
- if (clr != null) {
- return clr;
- } else {
- return Color.BLACK;
- }
- }
-
- public final void setDefaultColor(Class extends RocketComponent> c, Color color) {
- if (color == null)
- return;
- putString("componentColors", c.getSimpleName(), stringifyColor(color));
- }
-
/**
* Retrieve a Line style for the given component.
@@ -1014,6 +992,29 @@ protected String get(String directory,
public abstract void setComponentFavorite(ComponentPreset preset, ComponentPreset.Type type, boolean favorite);
public abstract Set getComponentFavorites(ComponentPreset.Type type);
+
+
+ /*
+ NOTE: It is unusual for the UI Theme to be stored in the preferences instead of SwingPreferences. In fact, this code
+ is not pretty. Sometimes I just really hate Java and circular dependencies...
+ But the reason why this is implemented is because it would otherwise be an even bigger nightmare to fix unit tests
+ that use their own preferences... Also wasn't a fan of always casting the preferences to SwingPreferences.
+ */
+ /**
+ * Get the current theme used for the UI.
+ * @return the current theme
+ */
+ public Object getUITheme() {
+ return null;
+ }
+
+ /**
+ * Set the theme used for the UI.
+ * @param theme the theme to set
+ */
+ public void setUITheme(Object theme) {}
+
+
/*
* Within a holder class so they will load only when needed.
@@ -1032,19 +1033,6 @@ private static class StaticFieldHolder {
DEFAULT_LINE_STYLES.put(RocketComponent.class, LineStyle.SOLID.name());
DEFAULT_LINE_STYLES.put(MassObject.class, LineStyle.DASHED.name());
}
-
- private static final HashMap, String> DEFAULT_COLORS = new HashMap, String>();
-
- static {
- DEFAULT_COLORS.put(BodyComponent.class, "0,0,240");
- DEFAULT_COLORS.put(TubeFinSet.class, "0,0,200");
- DEFAULT_COLORS.put(FinSet.class, "0,0,200");
- DEFAULT_COLORS.put(LaunchLug.class, "0,0,180");
- DEFAULT_COLORS.put(RailButton.class, "0,0,180");
- DEFAULT_COLORS.put(InternalComponent.class, "170,0,100");
- DEFAULT_COLORS.put(MassObject.class, "0,0,0");
- DEFAULT_COLORS.put(RecoveryDevice.class, "255,0,0");
- }
}
private final List listeners = new ArrayList();
diff --git a/core/src/net/sf/openrocket/util/Color.java b/core/src/net/sf/openrocket/util/Color.java
index 7c3186d347..4f912bb4e6 100644
--- a/core/src/net/sf/openrocket/util/Color.java
+++ b/core/src/net/sf/openrocket/util/Color.java
@@ -66,6 +66,10 @@ public java.awt.Color toAWTColor() {
return new java.awt.Color(red, green, blue, alpha);
}
+ public static Color fromAWTColor(java.awt.Color AWTColor) {
+ return new Color(AWTColor.getRed(), AWTColor.getGreen(), AWTColor.getBlue(), AWTColor.getAlpha());
+ }
+
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
diff --git a/core/src/net/sf/openrocket/util/TestRockets.java b/core/src/net/sf/openrocket/util/TestRockets.java
index a6e5fd7ee2..f0012a0d51 100644
--- a/core/src/net/sf/openrocket/util/TestRockets.java
+++ b/core/src/net/sf/openrocket/util/TestRockets.java
@@ -1049,7 +1049,7 @@ public static Rocket makeFalcon9Heavy() {
boosterCone.setThickness(0.002);
//payloadFairingNoseCone.setLength(0.118);
//payloadFairingNoseCone.setAftRadius(0.052);
- boosterCone.setAftShoulderRadius( 0.051 );
+ boosterCone.setAftShoulderRadius( 0.0375 );
boosterCone.setAftShoulderLength( 0.02 );
boosterCone.setAftShoulderThickness( 0.001 );
boosterCone.setAftShoulderCapped( false );
diff --git a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
index 245948e04a..a790cfc076 100644
--- a/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
+++ b/core/test/net/sf/openrocket/masscalc/MassCalculatorTest.java
@@ -498,7 +498,7 @@ public void testFalcon9HComponentMasses() {
// ====== ====== ======
ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0);
{
- expMass = 0.0222459863653;
+ expMass = 0.0210923935430;
// think of the casts as an assert that ( child instanceof NoseCone) == true
NoseCone nose = (NoseCone) boosters.getChild(0);
compMass = nose.getComponentMass();
@@ -582,7 +582,7 @@ public void testFalcon9HComponentCM() {
// ====== ====== ======
ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0);
{
- expCMx = 0.055710581052;
+ expCMx = 0.053835211739;
// think of the casts as an assert that ( child instanceof NoseCone) == true
NoseCone nose = (NoseCone) boosters.getChild(0);
actCMx = nose.getComponentCG().x;
@@ -692,10 +692,10 @@ public void testFalcon9HComponentMOI() {
ParallelStage boosters = (ParallelStage) coreStage.getChild(0).getChild(0);
{
final NoseCone boosterNose = (NoseCone) boosters.getChild(0);
- expInertia = 1.82665797857e-5;
+ expInertia = 1.73189409900e-5;
compInertia = boosterNose.getRotationalInertia();
assertEquals(boosterNose.getName() + " Rotational MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
- expInertia = 1.96501191666e-7;
+ expInertia = 4.51796586171e-6;
compInertia = boosterNose.getLongitudinalInertia();
assertEquals(boosterNose.getName() + " Longitudinal MOI calculated incorrectly: ", expInertia, compInertia, EPSILON);
@@ -832,8 +832,8 @@ public void testFalcon9HBoosterStructureCM() {
final RigidBody actualData = MassCalculator.calculateStructure(config);
final Coordinate actualCM = actualData.getCM();
- double expMass = 0.608663395;
- double expCMx = 1.073157592;
+ double expMass = 0.6063562096;
+ double expCMx = 1.075056887;
assertEquals("Heavy Booster Mass is incorrect: ", expMass, actualCM.weight, EPSILON);
assertEquals("Heavy Booster CM.x is incorrect: ", expCMx, actualCM.x, EPSILON);
@@ -852,11 +852,11 @@ public void testFalcon9HBoosterLaunchCM() {
RigidBody actualBoosterLaunchData = MassCalculator.calculateLaunch(config);
double actualMass = actualBoosterLaunchData.getMass();
- double expectedMass = 1.592663395;
+ double expectedMass = 1.5903562096;
assertEquals(" Booster Launch Mass is incorrect: ", expectedMass, actualMass, EPSILON);
final Coordinate actualCM = actualBoosterLaunchData.getCM();
- double expectedCMx = 1.22216804;
+ double expectedCMx = 1.22310836;
Coordinate expCM = new Coordinate(expectedCMx, 0, 0, expectedMass);
assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, actualCM.x, EPSILON);
assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, actualCM.y, EPSILON);
@@ -876,8 +876,8 @@ public void testFalcon9HBoosterSpentCM() {
RigidBody spentData = MassCalculator.calculateBurnout(config);
Coordinate spentCM = spentData.getCM();
- double expSpentMass = 1.12066339;
- double expSpentCMx = 1.18334714;
+ double expSpentMass = 1.11835621;
+ double expSpentCMx = 1.18460423;
Coordinate expLaunchCM = new Coordinate(expSpentCMx, 0, 0, expSpentMass);
assertEquals(" Booster Launch Mass is incorrect: ", expLaunchCM.weight, spentCM.weight, EPSILON);
assertEquals(" Booster Launch CM.x is incorrect: ", expLaunchCM.x, spentCM.x, EPSILON);
@@ -942,11 +942,11 @@ public void testFalcon9HeavyBoosterSpentMOIs() {
RigidBody spent = MassCalculator.calculateBurnout(config);
- double expMOIRotational = 0.009205665421431532;
+ double expMOIRotational = 0.00919009169;
double boosterMOIRotational = spent.getRotationalInertia();
assertEquals(" Booster x-axis MOI is incorrect: ", expMOIRotational, boosterMOIRotational, EPSILON);
- double expMOI_tr = 0.0582250994240395;
+ double expMOI_tr = 0.0573781722;
double boosterMOI_tr = spent.getLongitudinalInertia();
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
@@ -962,9 +962,9 @@ public void testFalcon9HeavyBoosterLaunchMOIs() {
RigidBody launchData = MassCalculator.calculateLaunch(config);
- final double expIxx = 0.01226617242143153;
+ final double expIxx = 0.0122505987;
final double actIxx = launchData.getRotationalInertia();
- final double expIyy = 0.06455356411879717;
+ final double expIyy = 0.0635943662;
final double actIyy = launchData.getLongitudinalInertia();
assertEquals(" Booster x-axis MOI is incorrect: ", expIxx, actIxx, EPSILON);
@@ -1006,11 +1006,11 @@ public void testFalcon9HeavyBoosterStageMassOverride() {
assertEquals(" Booster Launch CM is incorrect: ", expCM, boosterSetCM);
// Validate MOI
- double expMOI_axial = 0.005885793421431532;
+ double expMOI_axial = 0.00587021969016;
double boosterMOI_xx = burnout.getRotationalInertia();
assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON);
- double expMOI_tr = 17.86133586701;
+ double expMOI_tr = 17.78076176335;
double boosterMOI_tr = burnout.getLongitudinalInertia();
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
@@ -1052,7 +1052,7 @@ public void testFalcon9HeavyComponentMassOverride() {
double expTotalMass = 3.3565872;
assertEquals(" Booster Launch Mass is incorrect: ", expTotalMass, boosterData.getMass(), EPSILON);
- double expCMx = 0.2835089882645608;
+ double expCMx = 0.2827156156318;
Coordinate expCM = new Coordinate(expCMx, 0, 0, expTotalMass);
assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, boosterCM.x, EPSILON);
assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, boosterCM.y, EPSILON);
@@ -1064,7 +1064,7 @@ public void testFalcon9HeavyComponentMassOverride() {
double boosterMOI_xx = boosterData.getRotationalInertia();
assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON);
- double expMOI_tr = 0.34567788938578525;
+ double expMOI_tr = 0.347611843243;
double boosterMOI_tr = boosterData.getLongitudinalInertia();
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
@@ -1095,11 +1095,11 @@ public void testFalcon9HeavyComponentCMxOverride() {
RigidBody structure = MassCalculator.calculateStructure(config);
- final double expMass = 0.6086633952494;
+ final double expMass = 0.6063562096046;
double calcTotalMass = structure.getMass();
assertEquals(" Booster Launch Mass is incorrect: ", expMass, calcTotalMass, EPSILON);
- final double expCMx = 0.5551303646438673;
+ final double expCMx = 0.55670993586431;
Coordinate expCM = new Coordinate(expCMx, 0, 0, expMass);
assertEquals(" Booster Launch CM.x is incorrect: ", expCM.x, structure.getCM().x, EPSILON);
assertEquals(" Booster Launch CM.y is incorrect: ", expCM.y, structure.getCM().y, EPSILON);
@@ -1107,11 +1107,11 @@ public void testFalcon9HeavyComponentCMxOverride() {
assertEquals(" Booster Launch CM is incorrect: ", expCM, structure.getCM());
// Validate MOI
- final double expMOI_axial = 0.005885793421;
+ final double expMOI_axial = 0.005870219690;
double boosterMOI_xx = structure.getRotationalInertia();
assertEquals(" Booster x-axis MOI is incorrect: ", expMOI_axial, boosterMOI_xx, EPSILON);
- final double expMOI_tr = 0.040989095911;
+ final double expMOI_tr = 0.040598624476;
double boosterMOI_tr = structure.getLongitudinalInertia();
assertEquals(" Booster transverse MOI is incorrect: ", expMOI_tr, boosterMOI_tr, EPSILON);
}
diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
index 5340eefc01..fe80e83c89 100644
--- a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
+++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java
@@ -117,7 +117,7 @@ public void testMultiStage() throws SimulationException {
new FlightEvent(FlightEvent.Type.IGNITION, 0.0, coreBody),
new FlightEvent(FlightEvent.Type.LIFTOFF, 0.1225, null),
new FlightEvent(FlightEvent.Type.LAUNCHROD, 0.125, null),
- new FlightEvent(FlightEvent.Type.APOGEE, 1.86, rocket),
+ new FlightEvent(FlightEvent.Type.APOGEE, 1.867, rocket),
new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, boosterMotorTubes),
new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, boosterStage),
new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage),
@@ -147,7 +147,7 @@ public void testMultiStage() throws SimulationException {
new FlightEvent(FlightEvent.Type.BURNOUT, 2.0, boosterMotorTubes),
new FlightEvent(FlightEvent.Type.EJECTION_CHARGE, 2.0, boosterStage),
new FlightEvent(FlightEvent.Type.STAGE_SEPARATION, 2.0, boosterStage),
- new FlightEvent(FlightEvent.Type.TUMBLE, 3.551, null),
+ new FlightEvent(FlightEvent.Type.TUMBLE, 3.428, null),
new FlightEvent(FlightEvent.Type.GROUND_HIT, 1200, null),
new FlightEvent(FlightEvent.Type.SIMULATION_END, 1200, null)
};
diff --git a/install4j/23.xx/openrocket-23.xx.install4j b/install4j/23.xx/openrocket-23.xx.install4j
index 10a55f19a8..c9c664d8f1 100644
--- a/install4j/23.xx/openrocket-23.xx.install4j
+++ b/install4j/23.xx/openrocket-23.xx.install4j
@@ -48,6 +48,7 @@
+
diff --git a/swing/.classpath b/swing/.classpath
index d57e69a4c4..5aa1c307ee 100644
--- a/swing/.classpath
+++ b/swing/.classpath
@@ -11,6 +11,7 @@
+
diff --git a/swing/OpenRocket Swing.iml b/swing/OpenRocket Swing.iml
index c9df46ca4e..55f1c84b1c 100644
--- a/swing/OpenRocket Swing.iml
+++ b/swing/OpenRocket Swing.iml
@@ -5,6 +5,7 @@
+
@@ -107,6 +108,39 @@
+
+
+
+
+
+
+
+
+
+
+
+ ""
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/swing/build.xml b/swing/build.xml
index d5b5070b5a..80ef81afec 100644
--- a/swing/build.xml
+++ b/swing/build.xml
@@ -74,7 +74,7 @@
Java/JVM detail version: ${java.version}
Compiling main classes
-
+
@@ -116,6 +116,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/swing/lib/darklaf/darklaf-compatibility-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-compatibility-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..22945e1ff8
Binary files /dev/null and b/swing/lib/darklaf/darklaf-compatibility-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-core-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-core-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..964cae4b14
Binary files /dev/null and b/swing/lib/darklaf/darklaf-core-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-iconset-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-iconset-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..e355b8786d
Binary files /dev/null and b/swing/lib/darklaf/darklaf-iconset-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-macos-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-macos-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..f2cc41960b
Binary files /dev/null and b/swing/lib/darklaf/darklaf-macos-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-native-utils-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-native-utils-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..26fdbb0abb
Binary files /dev/null and b/swing/lib/darklaf/darklaf-native-utils-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-platform-base-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-platform-base-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..6b91bc95e2
Binary files /dev/null and b/swing/lib/darklaf/darklaf-platform-base-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-platform-decorations-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-platform-decorations-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..246e8d1b12
Binary files /dev/null and b/swing/lib/darklaf/darklaf-platform-decorations-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-platform-preferences-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-platform-preferences-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..b1768e88f0
Binary files /dev/null and b/swing/lib/darklaf/darklaf-platform-preferences-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-property-loader-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-property-loader-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..1db5e00e01
Binary files /dev/null and b/swing/lib/darklaf/darklaf-property-loader-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-theme-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-theme-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..b39af8f8bf
Binary files /dev/null and b/swing/lib/darklaf/darklaf-theme-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-theme-spec-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-theme-spec-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..e8119013f3
Binary files /dev/null and b/swing/lib/darklaf/darklaf-theme-spec-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-utils-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-utils-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..8e22ee456a
Binary files /dev/null and b/swing/lib/darklaf/darklaf-utils-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/darklaf-windows-3.0.3-SNAPSHOT.jar b/swing/lib/darklaf/darklaf-windows-3.0.3-SNAPSHOT.jar
new file mode 100644
index 0000000000..139299518e
Binary files /dev/null and b/swing/lib/darklaf/darklaf-windows-3.0.3-SNAPSHOT.jar differ
diff --git a/swing/lib/darklaf/jsvg-0.0.9.jar b/swing/lib/darklaf/jsvg-0.0.9.jar
new file mode 100644
index 0000000000..e8fbeba384
Binary files /dev/null and b/swing/lib/darklaf/jsvg-0.0.9.jar differ
diff --git a/swing/lib/darklaf/swing-extensions-laf-support-0.1.3.jar b/swing/lib/darklaf/swing-extensions-laf-support-0.1.3.jar
new file mode 100644
index 0000000000..2d2ec6b86a
Binary files /dev/null and b/swing/lib/darklaf/swing-extensions-laf-support-0.1.3.jar differ
diff --git a/swing/lib/darklaf/swing-extensions-visual-padding-0.1.3.jar b/swing/lib/darklaf/swing-extensions-visual-padding-0.1.3.jar
new file mode 100644
index 0000000000..5b39901e5b
Binary files /dev/null and b/swing/lib/darklaf/swing-extensions-visual-padding-0.1.3.jar differ
diff --git a/swing/src/net/sf/openrocket/communication/AssetHandler.java b/swing/src/net/sf/openrocket/communication/AssetHandler.java
index a04d0080b6..b0aa4d7b17 100644
--- a/swing/src/net/sf/openrocket/communication/AssetHandler.java
+++ b/swing/src/net/sf/openrocket/communication/AssetHandler.java
@@ -2,12 +2,10 @@
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.util.BuildProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
/**
* This class handles assets extracted from a GitHub release page.
diff --git a/swing/src/net/sf/openrocket/database/ComponentPresetDatabaseLoader.java b/swing/src/net/sf/openrocket/database/ComponentPresetDatabaseLoader.java
index ea09614628..177188785c 100644
--- a/swing/src/net/sf/openrocket/database/ComponentPresetDatabaseLoader.java
+++ b/swing/src/net/sf/openrocket/database/ComponentPresetDatabaseLoader.java
@@ -3,9 +3,7 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.io.ObjectInputStream;
import java.util.Collection;
-import java.util.List;
import net.sf.openrocket.file.iterator.DirectoryIterator;
import net.sf.openrocket.file.iterator.FileIterator;
@@ -14,7 +12,6 @@
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.xml.OpenRocketComponentLoader;
import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Pair;
import org.slf4j.Logger;
diff --git a/swing/src/net/sf/openrocket/gui/components/BasicTree.java b/swing/src/net/sf/openrocket/gui/components/BasicTree.java
index 09d8b3d727..c3d75231e4 100644
--- a/swing/src/net/sf/openrocket/gui/components/BasicTree.java
+++ b/swing/src/net/sf/openrocket/gui/components/BasicTree.java
@@ -1,5 +1,7 @@
package net.sf.openrocket.gui.components;
+import net.sf.openrocket.gui.util.GUIUtil;
+
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
@@ -14,7 +16,6 @@
@SuppressWarnings("serial")
public class BasicTree extends JTree {
-
public BasicTree() {
super();
setDefaultOptions();
@@ -36,7 +37,7 @@ private void setDefaultOptions() {
plainUI.setLeftChildIndent(15);
- this.setBackground(Color.WHITE);
+ this.setBackground(GUIUtil.getUITheme().getBackgroundColor());
this.setShowsRootHandles(false);
}
diff --git a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
index 2c666eff20..947f70335a 100644
--- a/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
+++ b/swing/src/net/sf/openrocket/gui/components/DescriptionArea.java
@@ -1,9 +1,9 @@
package net.sf.openrocket.gui.components;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.URLUtil;
import java.awt.Color;
-import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
@@ -31,6 +31,8 @@
public class DescriptionArea extends JScrollPane {
private final JEditorPane editorPane;
+
+ private final float size;
/**
@@ -86,15 +88,14 @@ public DescriptionArea(String text, int rows) {
public DescriptionArea(String text, int rows, float size, boolean opaque) {
super(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ this.size = size;
editorPane = new JEditorPane("text/html", "");
- Font font = editorPane.getFont();
- editorPane.setFont(font.deriveFont(font.getSize2D() + size));
editorPane.setEditable(false);
editorPane.addHyperlinkListener(new HyperlinkListener() {
public void hyperlinkUpdate(HyperlinkEvent e) {
if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
- URI uri = null;
+ URI uri;
try {
uri = e.getURL().toURI();
}
@@ -125,8 +126,8 @@ public void hyperlinkUpdate(HyperlinkEvent e) {
// create temporary file and copy resource to it
- File of = null;
- BufferedOutputStream os = null;
+ File of;
+ BufferedOutputStream os;
try {
of = File.createTempFile(prefix, suffix);
os = new BufferedOutputStream(new FileOutputStream(of));
@@ -163,11 +164,11 @@ public void hyperlinkUpdate(HyperlinkEvent e) {
}
// Calculate correct height
- editorPane.setText("abc");
+ this.setText("abc");
Dimension oneline = editorPane.getPreferredSize();
- editorPane.setText("abc
def");
+ this.setText("abc
def");
Dimension twolines = editorPane.getPreferredSize();
- editorPane.setText("");
+ this.setText("");
int lineheight = twolines.height - oneline.height;
int extraheight = oneline.height - lineheight;
@@ -175,13 +176,20 @@ public void hyperlinkUpdate(HyperlinkEvent e) {
Dimension dim = editorPane.getPreferredSize();
dim.height = lineheight * rows + extraheight + 2;
this.setPreferredSize(dim);
+
+ editorPane.setBorder(GUIUtil.getUITheme().getBorder());
this.setViewportView(editorPane);
this.setText(text);
}
public void setText(String txt) {
- editorPane.setText(txt);
+ // Set the font size (we can't simply set the font to change the font size, because we're using text/html)
+ Font defaultFont = editorPane.getFont();
+ String fontName = defaultFont.getFontName();
+ float fontSize = defaultFont.getSize2D() + size;
+
+ editorPane.setText("" + txt + "");
editorPane.revalidate();
SwingUtilities.invokeLater(new Runnable() {
diff --git a/swing/src/net/sf/openrocket/gui/components/URLLabel.java b/swing/src/net/sf/openrocket/gui/components/URLLabel.java
index 5656eaa96c..3d9a45321c 100644
--- a/swing/src/net/sf/openrocket/gui/components/URLLabel.java
+++ b/swing/src/net/sf/openrocket/gui/components/URLLabel.java
@@ -1,23 +1,18 @@
package net.sf.openrocket.gui.components;
-import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.TextAttribute;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.URLUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import net.sf.openrocket.util.BugException;
-
/**
* A label of a URL that is clickable. Clicking the URL will launch the URL in
* the default browser if the Desktop class is supported.
@@ -26,7 +21,7 @@
*/
public class URLLabel extends SelectableLabel {
private static final Logger log = LoggerFactory.getLogger(URLLabel.class);
-
+
/**
* Create a label showing the url it will direct to.
*
@@ -53,7 +48,7 @@ public URLLabel(final String url, String label) {
Map map = new HashMap();
map.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
this.setFont(this.getFont().deriveFont(map));
- this.setForeground(Color.BLUE);
+ this.setForeground(GUIUtil.getUITheme().getURLColor());
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
diff --git a/swing/src/net/sf/openrocket/gui/components/compass/Tester.java b/swing/src/net/sf/openrocket/gui/components/compass/Tester.java
index 97ca176b29..edac037995 100644
--- a/swing/src/net/sf/openrocket/gui/components/compass/Tester.java
+++ b/swing/src/net/sf/openrocket/gui/components/compass/Tester.java
@@ -22,7 +22,7 @@ public static void main(String[] args) throws InterruptedException, InvocationTa
BasicApplication baseApp = new BasicApplication();
baseApp.initializeApplication();
- GUIUtil.setBestLAF();
+ GUIUtil.applyLAF();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java
index d0569ea2f5..f35e5dba20 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/AppearancePanel.java
@@ -2,7 +2,6 @@
import java.awt.Color;
import java.awt.Component;
-import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.Method;
@@ -170,6 +169,7 @@ public void actionPerformed(ActionEvent colorClickEvent) {
Color awtColor = ColorConversion.toAwtColor(c);
colorChooser.setColor(awtColor);
+ colorChooser.updateUI(); // Needed for darklaf color chooser to update
// Bind a change of color selection to a change in the components color
ColorSelectionModel model = colorChooser.getSelectionModel();
@@ -183,7 +183,8 @@ public void stateChanged(ChangeEvent changeEvent) {
JDialog d = JColorChooser.createDialog(AppearancePanel.this,
trans.get("RocketCompCfg.lbl.Choosecolor"), true,
- colorChooser, new ActionListener() {
+ colorChooser,
+ new ActionListener() {
@Override
public void actionPerformed(ActionEvent okEvent) {
changeComponentColor(colorChooser.getColor());
@@ -262,8 +263,7 @@ public AppearancePanel(final OpenRocketDocument document, final RocketComponent
net.sf.openrocket.util.Color figureColor = c.getColor();
if (figureColor == null) {
- figureColor = Application.getPreferences().getDefaultColor(
- c.getClass());
+ figureColor = ((SwingPreferences) Application.getPreferences()).getDefaultColor(c.getClass());
}
final JButton figureColorButton = new SelectColorButton(
new ColorIcon(figureColor));
@@ -278,8 +278,7 @@ public AppearancePanel(final OpenRocketDocument document, final RocketComponent
public void stateChanged(EventObject e) {
net.sf.openrocket.util.Color col = c.getColor();
if (col == null) {
- col = Application.getPreferences().getDefaultColor(
- c.getClass());
+ col = ((SwingPreferences) Application.getPreferences()).getDefaultColor(c.getClass());
}
figureColorButton.setIcon(new ColorIcon(col));
}
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java
index 1d3ef71b83..98709e7344 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/InnerTubeConfig.java
@@ -9,6 +9,8 @@
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Ellipse2D;
@@ -17,12 +19,14 @@
import java.util.List;
import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
+import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
@@ -36,6 +40,7 @@
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.material.Material;
@@ -46,6 +51,7 @@
import net.sf.openrocket.rocketcomponent.RingComponent;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
@@ -55,6 +61,9 @@
public class InnerTubeConfig extends RocketComponentConfig {
private static final long serialVersionUID = 7900041420864324470L;
private static final Translator trans = Application.getTranslator();
+ private static final Preferences prefs = Application.getPreferences();
+
+ private static final String PREF_SEPARATION_RELATIVE = "InnerTubeSeparationRelative";
public InnerTubeConfig(OpenRocketDocument d, RocketComponent c, JDialog parent) {
@@ -279,29 +288,88 @@ private JPanel clusterTab() {
//// The separation of the tubes, 1.0 = touching each other
l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
subPanel.add(l);
- DoubleModel dm = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
- JSpinner spin = new JSpinner(dm.getSpinnerModel());
- spin.setEditor(new SpinnerEditor(spin));
- //// The separation of the tubes, 1.0 = touching each other
- spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
- subPanel.add(spin, "growx");
- order.add(((SpinnerEditor) spin.getEditor()).getTextField());
+ //// Models
+ final boolean useRelativeSeparation = prefs.getBoolean(PREF_SEPARATION_RELATIVE, true);
+ final DoubleModel clusterScaleModelRel = new DoubleModel(component, "ClusterScale", 1, UnitGroup.UNITS_NONE, 0);
+ final DoubleModel clusterScaleModelAbs = new DoubleModel(component, "ClusterScaleAbsolute", 1, UnitGroup.UNITS_LENGTH);
+ final DoubleModel clusterScaleModel = useRelativeSeparation ? clusterScaleModelRel : clusterScaleModelAbs;
+
+ final String clusterScaleTtipRel = trans.get("InnerTubeCfg.lbl.ttip.TubeSep");
+ final String clusterScaleTtipAbs = trans.get("InnerTubeCfg.lbl.ttip.TubeSepAbs");
+ final String clusterScaleTtip = useRelativeSeparation ? clusterScaleTtipRel : clusterScaleTtipAbs;
+
+ JSpinner clusterScaleSpin = new JSpinner(clusterScaleModel.getSpinnerModel());
+ clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
+ clusterScaleSpin.setToolTipText(clusterScaleTtip);
+ subPanel.add(clusterScaleSpin, "growx");
+ order.add(((SpinnerEditor) clusterScaleSpin.getEditor()).getTextField());
+
+ UnitSelector clusterScaleUnit = new UnitSelector(clusterScaleModel);
+ subPanel.add(clusterScaleUnit, "growx");
+
+ BasicSlider clusterScaleBs = new BasicSlider(clusterScaleModel.getSliderModel(0, 1, 4));
+ subPanel.add(clusterScaleBs, "w 100lp, wrap");
+
+ // Relative/absolute separation
+ JRadioButton rbRel = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Relative"));
+ JRadioButton rbAbs = new JRadioButton(trans.get("InnerTubeCfg.radioBut.Absolute"));
+ rbRel.setToolTipText(trans.get("InnerTubeCfg.radioBut.Relative.ttip"));
+ rbAbs.setToolTipText(trans.get("InnerTubeCfg.radioBut.Absolute.ttip"));
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(rbRel);
+ bg.add(rbAbs);
+ subPanel.add(rbRel, "skip, spanx, split 2");
+ subPanel.add(rbAbs, "wrap");
+
+ rbRel.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.DESELECTED)
+ return;
+ clusterScaleSpin.setModel(clusterScaleModelRel.getSpinnerModel());
+ clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
+ clusterScaleUnit.setModel(clusterScaleModelRel);
+ clusterScaleBs.setModel(clusterScaleModelRel.getSliderModel(0, 1, 4));
+ clusterScaleSpin.setToolTipText(clusterScaleTtipRel);
+
+ prefs.putBoolean(PREF_SEPARATION_RELATIVE, false);
+ }
+ });
+ rbAbs.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.DESELECTED)
+ return;
+ DoubleModel radiusModelMin = new DoubleModel(component, "OuterRadius", -2, UnitGroup.UNITS_LENGTH);
+ DoubleModel radiusModelMax = new DoubleModel(component, "OuterRadius", 6, UnitGroup.UNITS_LENGTH);
+
+ clusterScaleSpin.setModel(clusterScaleModelAbs.getSpinnerModel());
+ clusterScaleSpin.setEditor(new SpinnerEditor(clusterScaleSpin));
+ clusterScaleUnit.setModel(clusterScaleModelAbs);
+ clusterScaleBs.setModel(clusterScaleModelAbs.getSliderModel(radiusModelMin, radiusModelMax));
+ clusterScaleSpin.setToolTipText(clusterScaleTtipAbs);
+
+ prefs.putBoolean(PREF_SEPARATION_RELATIVE, false);
+ }
+ });
- BasicSlider bs = new BasicSlider(dm.getSliderModel(0, 1, 4));
- //// The separation of the tubes, 1.0 = touching each other
- bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.TubeSep"));
- subPanel.add(bs, "skip,w 100lp, wrap");
+ // Select the button by default
+ if (prefs.getBoolean(PREF_SEPARATION_RELATIVE, true)) {
+ rbRel.setSelected(true);
+ } else {
+ rbAbs.setSelected(true);
+ }
// Rotation:
l = new JLabel(trans.get("InnerTubeCfg.lbl.Rotation"));
//// Rotation angle of the cluster configuration
l.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
subPanel.add(l);
- dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
+ DoubleModel dm = new DoubleModel(component, "ClusterRotation", 1, UnitGroup.UNITS_ANGLE,
-Math.PI, Math.PI);
- spin = new JSpinner(dm.getSpinnerModel());
+ JSpinner spin = new JSpinner(dm.getSpinnerModel());
spin.setEditor(new SpinnerEditor(spin));
//// Rotation angle of the cluster configuration
spin.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
@@ -309,7 +377,7 @@ private JPanel clusterTab() {
order.add(((SpinnerEditor) spin.getEditor()).getTextField());
subPanel.add(new UnitSelector(dm), "growx");
- bs = new BasicSlider(dm.getSliderModel());
+ BasicSlider bs = new BasicSlider(dm.getSliderModel());
//// Rotation angle of the cluster configuration
bs.setToolTipText(trans.get("InnerTubeCfg.lbl.ttip.Rotation"));
subPanel.add(bs, "w 100lp, wrap para");
@@ -398,10 +466,17 @@ class ClusterSelectionPanel extends JPanel {
private static final int BUTTON_SIZE = 50;
private static final int MOTOR_DIAMETER = 10;
- private static final Color SELECTED_COLOR = Color.RED;
- private static final Color UNSELECTED_COLOR = Color.WHITE;
- private static final Color MOTOR_FILL_COLOR = Color.GREEN;
- private static final Color MOTOR_BORDER_COLOR = Color.BLACK;
+ private static final Color SELECTED_COLOR;
+ private static final Color UNSELECTED_COLOR;
+ private static final Color MOTOR_FILL_COLOR;
+ private static final Color MOTOR_BORDER_COLOR;
+
+ static {
+ SELECTED_COLOR = Color.RED;
+ UNSELECTED_COLOR = GUIUtil.getUITheme().getBackgroundColor();
+ MOTOR_FILL_COLOR = Color.GREEN;
+ MOTOR_BORDER_COLOR = Color.BLACK;
+ }
public ClusterSelectionPanel(Clusterable component) {
super(new MigLayout("gap 0 0",
@@ -438,6 +513,7 @@ public ClusterButton(Clusterable c, ClusterConfiguration config) {
setMaximumSize(new Dimension(BUTTON_SIZE, BUTTON_SIZE));
setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
// setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
+ setToolTipText(config.getXMLName());
component.addChangeListener(this);
addMouseListener(this);
}
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
index fb9dc9a954..6a8dad3ef2 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketComponentConfig.java
@@ -506,7 +506,7 @@ private JPanel overrideTab() {
StyledLabel labelMassOverriddenBy = new StyledLabel(
String.format(trans.get("RocketCompCfg.lbl.MassOverriddenBy"), component.getMassOverriddenBy().getName()),
0, StyledLabel.Style.BOLD);
- labelMassOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ labelMassOverriddenBy.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
labelMassOverriddenBy.setToolTipText(
String.format(trans.get("RocketCompCfg.lbl.MassOverriddenBy.ttip"), component.getMassOverriddenBy().getName()));
checkboxes.add(labelMassOverriddenBy, "gapleft 25lp, wrap");
@@ -569,7 +569,7 @@ private JPanel overrideTab() {
StyledLabel labelCGOverriddenBy = new StyledLabel(
String.format(trans.get("RocketCompCfg.lbl.CGOverriddenBy"), component.getCGOverriddenBy().getName()),
0, StyledLabel.Style.BOLD);
- labelCGOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ labelCGOverriddenBy.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
labelCGOverriddenBy.setToolTipText(
String.format(trans.get("RocketCompCfg.lbl.CGOverriddenBy.ttip"), component.getCGOverriddenBy().getName()));
checkboxes.add(labelCGOverriddenBy, "gapleft 25lp, wrap");
@@ -663,7 +663,7 @@ private JPanel overrideTab() {
StyledLabel labelCDOverriddenBy = new StyledLabel(
String.format(trans.get("RocketCompCfg.lbl.CDOverriddenBy"), component.getCDOverriddenBy().getName()),
0, StyledLabel.Style.BOLD);
- labelCDOverriddenBy.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ labelCDOverriddenBy.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
labelCDOverriddenBy.setToolTipText(
String.format(trans.get("RocketCompCfg.lbl.CDOverriddenBy"), component.getCDOverriddenBy().getName()));
checkboxes.add(labelCDOverriddenBy, "gapleft 25lp, wrap");
@@ -719,6 +719,7 @@ private JPanel commentTab() {
commentTextArea.setLineWrap(true);
commentTextArea.setWrapStyleWord(true);
commentTextArea.setEditable(true);
+ commentTextArea.setBorder(GUIUtil.getUITheme().getBorder());
GUIUtil.setTabToFocusing(commentTextArea);
commentTextArea.addFocusListener(textFieldListener);
commentTextArea.addKeyListener(new TextComponentSelectionKeyListener(commentTextArea));
diff --git a/swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java b/swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java
index acd4ee47bf..9835778ca5 100644
--- a/swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java
+++ b/swing/src/net/sf/openrocket/gui/configdialog/RocketConfig.java
@@ -27,7 +27,7 @@
public class RocketConfig extends RocketComponentConfig {
private static final Translator trans = Application.getTranslator();
-
+
private TextFieldListener textFieldListener;
private JTextArea designerTextArea;
@@ -55,6 +55,7 @@ public RocketConfig(OpenRocketDocument d, RocketComponent c, JDialog parent) {
designerTextArea.setLineWrap(true);
designerTextArea.setWrapStyleWord(true);
designerTextArea.setEditable(true);
+ designerTextArea.setBorder(GUIUtil.getUITheme().getBorder());
GUIUtil.setTabToFocusing(designerTextArea);
designerTextArea.addFocusListener(textFieldListener);
this.add(new JScrollPane(designerTextArea), "wmin 400lp, height 60lp:60lp:, grow 30, wrap para");
@@ -69,6 +70,7 @@ public RocketConfig(OpenRocketDocument d, RocketComponent c, JDialog parent) {
revisionTextArea.setLineWrap(true);
revisionTextArea.setWrapStyleWord(true);
revisionTextArea.setEditable(true);
+ revisionTextArea.setBorder(GUIUtil.getUITheme().getBorder());
GUIUtil.setTabToFocusing(revisionTextArea);
revisionTextArea.addFocusListener(textFieldListener);
diff --git a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java
index 394daec86e..c37e8ed2b7 100644
--- a/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java
+++ b/swing/src/net/sf/openrocket/gui/customexpression/CustomExpressionPanel.java
@@ -1,6 +1,5 @@
package net.sf.openrocket.gui.customexpression;
-import java.awt.Color;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -18,6 +17,7 @@
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileNameExtensionFilter;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,7 +39,7 @@ public class CustomExpressionPanel extends JPanel {
private static final Logger log = LoggerFactory.getLogger(CustomExpressionPanel.class);
private static final Translator trans = Application.getTranslator();
-
+
private JPanel expressionSelectorPanel;
private OpenRocketDocument doc;
@@ -51,6 +51,7 @@ public CustomExpressionPanel(final OpenRocketDocument doc, final JDialog parentD
expressionSelectorPanel.setToolTipText(trans.get("customExpressionPanel.lbl.CalcNote"));
JScrollPane scroll = new JScrollPane(expressionSelectorPanel);
+ expressionSelectorPanel.setBorder(GUIUtil.getUITheme().getBorder());
//Border bdr = BorderFactory.createTitledBorder(trans.get("customExpressionPanel.lbl.CustomExpressions"));
//scroll.setBorder(bdr);
@@ -170,10 +171,10 @@ private void moveExpression(CustomExpression expression, int move) {
* A JPanel which configures a single expression
*/
private class SingleExpression extends JPanel {
-
+
// Convenience method to make the labels consistent
private JLabel setLabelStyle(JLabel l) {
- l.setBackground(Color.WHITE);
+ l.setBackground(GUIUtil.getUITheme().getBackgroundColor());
l.setOpaque(true);
l.setBorder(BorderFactory.createRaisedBevelBorder());
l.setText(" " + l.getText() + " ");
@@ -191,13 +192,13 @@ private SingleExpression(final CustomExpression expression, boolean showUp, bool
JLabel symbolLabel = new JLabel(trans.get("customExpression.Symbol") + " :");
JLabel symbol = new JLabel(expression.getSymbol());
symbol = setLabelStyle(symbol);
- symbol.setBackground(Color.WHITE);
+ symbol.setBackground(GUIUtil.getUITheme().getBackgroundColor());
JLabel unitLabel = new JLabel(trans.get("customExpression.Units") + " :");
UnitSelector unitSelector = new UnitSelector(expression.getType().getUnitGroup());
//JLabel unitSelector = new JLabel ( expression.getUnit() );
//unitSelector = setLabelStyle(unitSelector);
- //unitSelector.setBackground(Color.WHITE);
+ //unitSelector.setBackground(GUIUtil.getUITheme().getBackgroundColor());
JButton editButton = new SelectColorButton(Icons.EDIT_EDIT);
editButton.setToolTipText(trans.get("customExpression.Units.but.ttip.Edit"));
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
index 4bc5fbf836..711dc0f1da 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/AboutDialog.java
@@ -43,7 +43,7 @@ public class AboutDialog extends JDialog {
"Daniel Williams (pod support, maintainer)
" +
"Joe Pfeiffer (maintainer)
" +
"Billy Olsen (maintainer)
" +
- "Sibo Van Gool (RASAero file format, 3D OBJ export, maintainer)
" +
+ "Sibo Van Gool (RASAero file format, 3D OBJ export, dark theme, maintainer)
" +
"Justin Hanney (maintainer)
" +
"Neil Weinstock (tester, icons, forum support)
" +
"H. Craig Miller (tester)
" +
@@ -60,6 +60,9 @@ public class AboutDialog extends JDialog {
"
" +
"See all contributors at
" + href("https://github.com/openrocket/openrocket/graphs/contributors", false, false) + "
" +
"
" +
+ "Thank you to our financial contributors who have provided us with the necessary resources to continue this project:
" +
+ href("https://opencollective.com/openrocket", true, true) + "
" +
+ "
" +
"OpenRocket utilizes the following libraries:
" +
"
" +
"MiG Layout" + href("http://www.miglayout.com", true, true) + "
" +
@@ -72,7 +75,8 @@ public class AboutDialog extends JDialog {
"Simple Logging Facade for Java" + href("http://www.slf4j.org", true, true) + "
" +
"Java library for parsing and rendering CommonMark" + href("https://github.com/commonmark/commonmark-java", true, true) + "
" +
"RSyntaxTextArea" + href("http://bobbylight.github.io/RSyntaxTextArea", true, true) + "
" +
- "Obj" + href("https://github.com/javagl/Obj", true, true) + "
" +
+ "Darklaf (dark theme)" + href("https://github.com/weisJ/darklaf", true, true) + "
" +
+ "Obj" + href("https://github.com/javagl/Obj", true, true) + "
" +
"
" +
"OpenRocket gratefully acknowledges our use of the following databases:
" +
"
" +
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
index 763f873b0c..117384b512 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/BugReportDialog.java
@@ -1,5 +1,6 @@
package net.sf.openrocket.gui.dialogs;
+import java.awt.Color;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Window;
@@ -177,8 +178,9 @@ public static void showExceptionDialog(Window parent, Thread t, Throwable e) {
private static void addBugReportInformation(StringBuilder sb) {
sb.append("---------- Bug report ----------\n");
sb.append('\n');
- sb.append("Please include a description about what actions you were " +
- "performing when the exception occurred:\n");
+ Color color = GUIUtil.getUITheme().getDarkWarningColor();
+ sb.append(String.format("Please include a description about what actions you were " +
+ "performing when the exception occurred:\n", color.getRed(), color.getGreen(), color.getBlue()));
sb.append("(You can edit text directly in this window)\n");
sb.append('\n');
sb.append("1. \n");
@@ -204,6 +206,7 @@ private static void addSystemInformation(StringBuilder sb) {
sbTemp.append("OpenRocket source: " + BuildProperties.getBuildSource() + "\n");
sbTemp.append("OpenRocket location: " + JarUtil.getCurrentJarFile() + "\n");
sbTemp.append("User-defined thrust curves location: " + preferences.getUserThrustCurveFilesAsString() + "\n");
+ sbTemp.append("LAF: " + UIManager.getLookAndFeel().getClass().getName() + "\n");
sbTemp.append("JOGL version: " + JoglVersion.getInstance().getImplementationVersion() + "\n");
sbTemp.append("Current default locale: " + Locale.getDefault() + "\n");
sbTemp.append("System properties:\n");
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
index 12cd39195f..2a9d598d7e 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/ComponentAnalysisDialog.java
@@ -150,6 +150,7 @@ public void stateChanged(ChangeEvent e) {
warningList = new JList<>();
JScrollPane scrollPane = new JScrollPane(warningList);
+ warningList.setBorder(GUIUtil.getUITheme().getBorder());
////Warnings:
scrollPane.setBorder(BorderFactory.createTitledBorder(trans.get("componentanalysisdlg.TitledBorder.warnings")));
panel.add(scrollPane, "gap paragraph, spany 4, w 300lp, grow, height :100lp:, wrap");
@@ -646,7 +647,7 @@ public Component getTableCellRendererComponent(JTable table, Object value,
}
label.setOpaque(true);
- label.setBackground(Color.WHITE);
+ label.setBackground(GUIUtil.getUITheme().getBackgroundColor());
label.setHorizontalAlignment(SwingConstants.LEFT);
if ((row < 0) || (row >= data.size()))
@@ -693,7 +694,6 @@ public Component getTableCellRendererComponent(JTable table, Object value,
private class DragCellRenderer extends CustomCellRenderer {
private static final long serialVersionUID = 1L;
-
public DragCellRenderer() {
super(dragData, 3);
}
@@ -712,6 +712,7 @@ public Component getTableCellRendererComponent(JTable table, Object value,
float val = 1.0f;
label.setBackground(Color.getHSBColor(hue, sat, val));
+ label.setForeground(Color.BLACK);
}
if ((row < 0) || (row >= dragData.size()))
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
index 4a2e817940..26bbd5ddfc 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/DebugLogDialog.java
@@ -67,7 +67,7 @@ public class DebugLogDialog extends JDialog {
private static final int POLL_TIME = 250;
private static final String STACK_TRACE_MARK = "\uFF01";
private static final Translator trans = Application.getTranslator();
-
+
private static final EnumMap backgroundColors = new EnumMap(LogLevel.class);
static {
for (LogLevel l : LogLevel.values()) {
@@ -343,6 +343,7 @@ public void valueChanged(ListSelectionEvent e) {
bottomPanel.add(new JLabel(trans.get("debuglogdlg.lbl.Stacktrace")), "wrap rel");
stackTraceLabel = new JTextArea(8, 80);
stackTraceLabel.setEditable(false);
+ stackTraceLabel.setBorder(GUIUtil.getUITheme().getBorder());
GUIUtil.changeFontSize(stackTraceLabel, -2);
bottomPanel.add(new JScrollPane(stackTraceLabel), "grow, pushy 200, growprioy 200");
@@ -503,7 +504,7 @@ public Component getTableCellRendererComponent(JTable table1, Object value, bool
if (STACK_TRACE_MARK.equals(value)) {
fg = Color.RED;
} else {
- fg = table1.getForeground();
+ fg = Color.BLACK;
}
bg = backgroundColors.get(buffer.get(row).getLevel());
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java
index 335fe25b81..29e74dcde4 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/ErrorWarningDialog.java
@@ -3,6 +3,7 @@
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.BetterListCellRenderer;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.logging.Error;
import net.sf.openrocket.logging.ErrorSet;
import net.sf.openrocket.logging.Warning;
@@ -15,7 +16,6 @@
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.ListSelectionModel;
-import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
@@ -25,11 +25,12 @@
*/
@SuppressWarnings("serial")
public abstract class ErrorWarningDialog {
+
public static void showErrorsAndWarnings(Component parent, Object message, String title, ErrorSet errors, WarningSet warnings) {
JPanel content = new JPanel(new MigLayout("ins 0, fillx"));
StyledLabel label = new StyledLabel("Errors");
- label.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ label.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
content.add(label, "wrap, gaptop 15lp");
Error[] e = errors.toArray(new Error[0]);
@@ -37,6 +38,7 @@ public static void showErrorsAndWarnings(Component parent, Object message, Strin
errorList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
errorList.setCellRenderer(new ErrorListCellRenderer());
JScrollPane errorPane = new JScrollPane(errorList);
+ errorList.setBorder(GUIUtil.getUITheme().getBorder());
content.add(errorPane, "wrap, growx");
// Deselect items if clicked on blank region
@@ -58,6 +60,7 @@ public void mousePressed(MouseEvent e) {
final JList warningList = new JList<>(w);
warningList.setCellRenderer(new BetterListCellRenderer());
JScrollPane warningPane = new JScrollPane(warningList);
+ warningList.setBorder(GUIUtil.getUITheme().getBorder());
content.add(warningPane, "wrap, growx");
// Deselect items if clicked on blank region
@@ -84,9 +87,9 @@ public Component getListCellRendererComponent(JList> list, Object value, int i
// Text color
if (isSelected) {
- label.setForeground(Color.WHITE);
+ label.setForeground(GUIUtil.getUITheme().getTextSelectionForegroundColor());
} else {
- label.setForeground(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ label.setForeground(GUIUtil.getUITheme().getDarkWarningColor());
}
return label;
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
index 006222aedf..cf84659ca1 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/UpdateInfoDialog.java
@@ -1,16 +1,15 @@
package net.sf.openrocket.gui.dialogs;
import java.awt.Component;
-import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
@@ -75,6 +74,7 @@ public UpdateInfoDialog(UpdateInfo info) {
// Release information box
final JTextPane textPane = new JTextPane();
+ textPane.setBorder(BorderFactory.createLineBorder(GUIUtil.getUITheme().getTextColor()));
textPane.setEditable(false);
textPane.setContentType("text/html");
textPane.setMargin(new Insets(10, 10, 40, 10));
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java
index f856bd8a01..ec2a0a34a7 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/WarningDialog.java
@@ -2,23 +2,25 @@
import java.awt.Component;
+import javax.swing.BorderFactory;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import net.sf.openrocket.gui.util.BetterListCellRenderer;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.logging.Warning;
import net.sf.openrocket.logging.WarningSet;
@SuppressWarnings("serial")
public abstract class WarningDialog {
-
public static void showWarnings(Component parent, Object message, String title, WarningSet warnings) {
Warning[] w = warnings.toArray(new Warning[0]);
final JList list = new JList(w);
list.setCellRenderer(new BetterListCellRenderer());
JScrollPane pane = new JScrollPane(list);
+ pane.setBorder(GUIUtil.getUITheme().getBorder());
JOptionPane.showMessageDialog(parent, new Object[] { message, pane },
title, JOptionPane.WARNING_MESSAGE);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
index ee295ee9fc..88222ec545 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/flightconfiguration/RenameConfigDialog.java
@@ -1,6 +1,5 @@
package net.sf.openrocket.gui.dialogs.flightconfiguration;
-import java.awt.Color;
import java.awt.Dialog;
import java.awt.Window;
import java.awt.event.ActionEvent;
@@ -25,7 +24,7 @@
public class RenameConfigDialog extends JDialog {
private static final long serialVersionUID = -5423008694485357248L;
private static final Translator trans = Application.getTranslator();
-
+
public RenameConfigDialog(final Window parent, final Rocket rocket, final FlightConfigurationId fcid) {
super(parent, trans.get("RenameConfigDialog.title"), Dialog.ModalityType.APPLICATION_MODAL);
@@ -47,7 +46,7 @@ public void actionPerformed(ActionEvent e) {
RenameConfigDialog.this.setVisible(false);
}
});
- panel.add(okButton);
+ panel.add(okButton, "growx");
JButton renameToDefaultButton = new SelectColorButton(trans.get("RenameConfigDialog.but.reset"));
renameToDefaultButton.addActionListener(new ActionListener() {
@@ -57,7 +56,7 @@ public void actionPerformed(ActionEvent e) {
RenameConfigDialog.this.setVisible(false);
}
});
- panel.add(renameToDefaultButton);
+ panel.add(renameToDefaultButton, "growx");
JButton cancel = new SelectColorButton(trans.get("button.cancel"));
cancel.addActionListener(new ActionListener() {
@@ -66,14 +65,14 @@ public void actionPerformed(ActionEvent e) {
RenameConfigDialog.this.setVisible(false);
}
});
- panel.add(cancel, "wrap para");
+ panel.add(cancel, "growx, wrap para");
// {motors} & {manufacturers} info
String text = "" + CommonStrings.dagger + " " + trans.get("RenameConfigDialog.lbl.infoMotors")
+ trans.get("RenameConfigDialog.lbl.infoManufacturers")
+ trans.get("RenameConfigDialog.lbl.infoCombination");
StyledLabel info = new StyledLabel(text, -2);
- info.setFontColor(Color.DARK_GRAY);
+ info.setFontColor(GUIUtil.getUITheme().getDimTextColor());
panel.add(info, "spanx, growx, wrap");
this.add(panel);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
index dcbf04aa06..de63acf0e7 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/motor/thrustcurve/MotorInformationPanel.java
@@ -37,14 +37,13 @@
@SuppressWarnings("serial")
class MotorInformationPanel extends JPanel {
-
+ private static final Translator trans = Application.getTranslator();
+
private static final int ZOOM_ICON_POSITION_NEGATIVE_X = 50;
private static final int ZOOM_ICON_POSITION_POSITIVE_Y = 12;
- private static final Color NO_COMMENT_COLOR = Color.GRAY;
- private static final Color WITH_COMMENT_COLOR = Color.BLACK;
-
- private static final Translator trans = Application.getTranslator();
+ private static final Color NO_COMMENT_COLOR = GUIUtil.getUITheme().getDimTextColor();
+ private static final Color WITH_COMMENT_COLOR = GUIUtil.getUITheme().getTextColor();
// Motors in set
private List selectedMotorSet;
@@ -190,7 +189,9 @@ public MotorInformationPanel() {
changeLabelFont(plot.getDomainAxis(), -2);
//// Thrust curve:
- chart.setTitle(new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont()));
+ TextTitle title = new TextTitle(trans.get("TCMotorSelPan.title.Thrustcurve"), this.getFont());
+ title.setPaint(GUIUtil.getUITheme().getTextColor());
+ chart.setTitle(title);
chart.setBackgroundPaint(this.getBackground());
plot.setBackgroundPaint(Color.WHITE);
plot.setDomainGridlinePaint(Color.LIGHT_GRAY);
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java b/swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java
index 64b106336b..8718a48a60 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/optimization/SimulationModifierTree.java
@@ -1,6 +1,5 @@
package net.sf.openrocket.gui.dialogs.optimization;
-import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.util.Enumeration;
@@ -17,6 +16,7 @@
import net.sf.openrocket.gui.components.BasicTree;
import net.sf.openrocket.gui.main.ComponentIcons;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.rocketcomponent.Rocket;
@@ -37,7 +37,7 @@ public class SimulationModifierTree extends BasicTree {
private final List selectedModifiers;
private static final Translator trans = Application.getTranslator();
-
+
/**
* Sole constructor.
*
@@ -151,11 +151,13 @@ public Component getTreeCellRendererComponent(
// Set icon (for rocket components, null for others)
setIcon(ComponentIcons.getSmallIcon(object.getClass()));
-
+
+ // By default, set background to transparent
+ setOpaque(false);
// Set text color/style
if (object instanceof RocketComponent) {
- setForeground(Color.GRAY);
+ setForeground(GUIUtil.getUITheme().getDimTextColor());
setFont(componentFont);
// Set tooltip
@@ -169,21 +171,24 @@ public Component getTreeCellRendererComponent(
this.setToolTipText(null);
}
} else if (object instanceof String) {
- setForeground(Color.GRAY);
+ setForeground(GUIUtil.getUITheme().getDimTextColor());
setFont(stringFont);
} else if (object instanceof SimulationModifier) {
if (selectedModifiers.contains(object)) {
- setForeground(Color.GRAY);
+ setForeground(GUIUtil.getUITheme().getDimTextColor());
+ setFont(stringFont);
} else {
if (tree.getSelectionRows() != null &&
IntStream.of(tree.getSelectionRows()).anyMatch(r -> r == row)) {
- setForeground(Color.WHITE);
+ setForeground(GUIUtil.getUITheme().getTextSelectionForegroundColor());
+ setBackground(GUIUtil.getUITheme().getTextSelectionBackgroundColor());
+ setOpaque(true);
} else {
- setForeground(Color.BLACK);
+ setForeground(GUIUtil.getUITheme().getTextColor());
}
+ setFont(modifierFont);
}
- setFont(modifierFont);
setText(((SimulationModifier) object).getName());
setToolTipText(((SimulationModifier) object).getDescription());
}
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java
index 4457b97867..98e993c6f7 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/GeneralPreferencesPanel.java
@@ -20,9 +20,12 @@
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
+import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
@@ -31,6 +34,8 @@
import net.sf.openrocket.communication.UpdateInfo;
import net.sf.openrocket.communication.UpdateInfoRetriever;
import net.sf.openrocket.communication.UpdateInfoRetriever.ReleaseStatus;
+import net.sf.openrocket.gui.SpinnerEditor;
+import net.sf.openrocket.gui.adaptors.IntegerModel;
import net.sf.openrocket.gui.components.DescriptionArea;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
@@ -40,6 +45,7 @@
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.gui.util.PreferencesExporter;
import net.sf.openrocket.gui.util.PreferencesImporter;
+import net.sf.openrocket.gui.util.UITheme;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.startup.Preferences;
@@ -50,13 +56,17 @@
@SuppressWarnings("serial")
public class GeneralPreferencesPanel extends PreferencesPanel {
+ private final UITheme.Theme currentTheme;
+ private final int currentFontSize;
public GeneralPreferencesPanel(PreferencesDialog parent) {
super(parent, new MigLayout("fillx, ins 30lp n n n"));
-
+
+ this.currentTheme = GUIUtil.getUITheme();
+ this.currentFontSize = preferences.getUIFontSize();
//// Language selector
- Locale userLocale = null;
+ Locale userLocale;
{
String locale = preferences.getString("locale", null);
userLocale = L10N.toLocale(locale);
@@ -79,6 +89,7 @@ public GeneralPreferencesPanel(PreferencesDialog parent) {
@SuppressWarnings("unchecked")
public void actionPerformed(ActionEvent e) {
Named selection = (Named) languageCombo.getSelectedItem();
+ if (selection == null) return;
Locale l = selection.get();
preferences.putString(Preferences.USER_LOCAL, l == null ? null : l.toString());
}
@@ -86,8 +97,64 @@ public void actionPerformed(ActionEvent e) {
this.add(new JLabel(trans.get("generalprefs.lbl.language")), "gapright para");
this.add(languageCombo, "wrap rel, growx, sg combos");
- this.add(new StyledLabel(trans.get("generalprefs.lbl.languageEffect"), -3, Style.ITALIC), "span, wrap para*2");
-
+ this.add(new StyledLabel(trans.get("generalprefs.lbl.languageEffect"), -3, Style.ITALIC), "span, wrap rel");
+
+ //// UI Theme
+ UITheme.Theme currentTheme = GUIUtil.getUITheme();
+ List> themes = new ArrayList<>();
+ for (UITheme.Theme t : UITheme.Themes.values()) {
+ themes.add(new Named<>(t, t.getDisplayName()));
+ }
+ Collections.sort(themes);
+
+ final JComboBox> themesCombo = new JComboBox<>(themes.toArray());
+ for (int i = 0; i < themes.size(); i++) {
+ if (Utils.equals(currentTheme, themes.get(i).get())) {
+ themesCombo.setSelectedIndex(i);
+ }
+ }
+
+ this.add(new JLabel(trans.get("generalprefs.lbl.UITheme")), "gapright para");
+ this.add(themesCombo, "wrap, growx, sg combos");
+
+ //// Font size
+ this.add(new JLabel(trans.get("generalprefs.lbl.FontSize")), "gapright para");
+ final IntegerModel fontSizeModel = new IntegerModel(preferences, "UIFontSize", 5, 25);
+ final JSpinner fontSizeSpinner = new JSpinner(fontSizeModel.getSpinnerModel());
+ fontSizeSpinner.setEditor(new SpinnerEditor(fontSizeSpinner));
+ this.add(fontSizeSpinner, "growx, wrap");
+
+ //// You need to restart OpenRocket for the theme change to take effect.
+ final JLabel lblRestartORTheme = new JLabel();
+ lblRestartORTheme.setForeground(GUIUtil.getUITheme().getDarkWarningColor());
+ this.add(lblRestartORTheme, "spanx, wrap para*2, growx");
+
+ fontSizeSpinner.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ if (fontSizeModel.getValue() == currentFontSize) {
+ lblRestartORTheme.setText("");
+ return;
+ }
+ lblRestartORTheme.setText(trans.get("generalprefs.lbl.themeRestartOR"));
+ }
+ });
+ themesCombo.addActionListener(new ActionListener() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void actionPerformed(ActionEvent e) {
+ Named selection = (Named) themesCombo.getSelectedItem();
+ if (selection == null) return;
+ UITheme.Theme t = selection.get();
+ if (t == currentTheme) {
+ lblRestartORTheme.setText("");
+ return;
+ }
+ preferences.setUITheme(t);
+ lblRestartORTheme.setText(trans.get("generalprefs.lbl.themeRestartOR"));
+ }
+ });
+
//// User-defined thrust curves:
this.add(new JLabel(trans.get("pref.dlg.lbl.User-definedthrust")), "spanx, wrap");
final JTextField field = new JTextField();
@@ -180,8 +247,9 @@ public void actionPerformed(ActionEvent e) {
this.add(button, "wrap");
//// Add directories, RASP motor files (*.eng), RockSim engine files (*.rse) or ZIP archives separated by a semicolon (;) to load external thrust curves. Changes will take effect the next time you start OpenRocket.
- DescriptionArea desc = new DescriptionArea(trans.get("pref.dlg.DescriptionArea.Adddirectories"), 3, -3, false);
- desc.setBackground(getBackground());
+ DescriptionArea desc = new DescriptionArea(trans.get("pref.dlg.DescriptionArea.Adddirectories"), 3, -1.5f, false);
+ desc.setBackground(GUIUtil.getUITheme().getBackgroundColor());
+ desc.setForeground(GUIUtil.getUITheme().getTextColor());
this.add(desc, "spanx, growx, wrap 40lp");
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java
index 459c6f7477..2cb3869544 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/LaunchPreferencesPanel.java
@@ -23,6 +23,7 @@
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.models.atmosphere.ExtendedISAModel;
import net.sf.openrocket.simulation.SimulationOptions;
import net.sf.openrocket.unit.UnitGroup;
@@ -43,7 +44,7 @@ public LaunchPreferencesPanel() {
StyledLabel warning = new StyledLabel(String.format(
"%s", trans.get("pref.dlg.lbl.launchWarning")),
0.5f, StyledLabel.Style.BOLD);
- warning.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ warning.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
warning.setToolTipText(trans.get("pref.dlg.lbl.launchWarning.ttip"));
add(warning, "spanx, growx 0, gapbottom para, wrap");
diff --git a/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java b/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java
index 92bb729b38..051e312ac6 100644
--- a/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java
+++ b/swing/src/net/sf/openrocket/gui/dialogs/preferences/SimulationPreferencesPanel.java
@@ -17,6 +17,7 @@
import net.sf.openrocket.gui.components.BasicSlider;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.simulation.RK4SimulationStepper;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.GeodeticComputationStrategy;
@@ -24,7 +25,7 @@
public class SimulationPreferencesPanel extends PreferencesPanel {
private static final long serialVersionUID = 7983195730016979888L;
-
+
/*
* private GeodeticComputationStrategy geodeticComputation =
* GeodeticComputationStrategy.SPHERICAL;
@@ -41,7 +42,7 @@ public SimulationPreferencesPanel() {
confirmDelete.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- preferences.setAutoRunSimulations(confirmDelete.isSelected());
+ preferences.setConfirmSimDeletion(confirmDelete.isSelected());
}
});
this.add(confirmDelete, "wrap, growx, sg combos ");
@@ -84,7 +85,7 @@ public void actionPerformed(ActionEvent e) {
StyledLabel warning = new StyledLabel(String.format(
"%s", trans.get("pref.dlg.lbl.launchWarning")),
0, StyledLabel.Style.BOLD);
- warning.setFontColor(net.sf.openrocket.util.Color.DARK_RED.toAWTColor());
+ warning.setFontColor(GUIUtil.getUITheme().getDarkWarningColor());
warning.setToolTipText(trans.get("pref.dlg.lbl.launchWarning.ttip"));
subsub.add(warning, "spanx, wrap para");
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java
index 65df4d52f5..a9c2eacf40 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/FigureRenderer.java
@@ -10,6 +10,7 @@
import net.sf.openrocket.gui.figure3d.geometry.Geometry;
import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface;
+import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.rocketcomponent.BodyTube;
import net.sf.openrocket.rocketcomponent.ExternalComponent;
@@ -80,7 +81,7 @@ public void renderComponent(GL2 gl, Geometry geom, float alpha) {
if (defaultColorCache.containsKey(c.getClass())) {
figureColor = defaultColorCache.get(c.getClass());
} else {
- figureColor = Application.getPreferences().getDefaultColor(c.getClass());
+ figureColor = ((SwingPreferences) Application.getPreferences()).getDefaultColor(c.getClass());
defaultColorCache.put(c.getClass(), figureColor);
}
}
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
index 578817921c..41cac74ba6 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/RealisticRenderer.java
@@ -42,9 +42,9 @@ public void init(GLAutoDrawable drawable) {
gl.glLightModelfv(GL2ES1.GL_LIGHT_MODEL_AMBIENT, new float[] { 0, 0, 0 }, 0);
- float amb = 0.3f;
- float dif = 1.0f - amb;
- float spc = 1.0f;
+ float amb = 0.4f;
+ float dif = 0.65f;
+ float spc = 0.65f;
gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_AMBIENT, new float[] { amb, amb, amb, 1 }, 0);
gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_DIFFUSE, new float[] { dif, dif, dif, 1 }, 0);
gl.glLightfv(GLLightingFunc.GL_LIGHT1, GLLightingFunc.GL_SPECULAR, new float[] { spc, spc, spc, 1 }, 0);
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java b/swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java
index 38769db88a..cc65d95ae6 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/RocketFigure3d.java
@@ -34,6 +34,7 @@
import javax.swing.SwingUtilities;
import javax.swing.event.MouseInputAdapter;
+import net.sf.openrocket.gui.util.GUIUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,7 +65,7 @@ public class RocketFigure3d extends JPanel implements GLEventListener {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger(RocketFigure3d.class);
-
+
static {
//this allows the GL canvas and things like the motor selection
//drop down to z-order themselves.
@@ -288,7 +289,9 @@ public void display(final GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
GLU glu = new GLU();
- gl.glClearColor(1, 1, 1, 1);
+ Color backgroundColor = GUIUtil.getUITheme().getBackgroundColor();
+ gl.glClearColor(backgroundColor.getRed()/255f, backgroundColor.getGreen()/255f,
+ backgroundColor.getBlue()/255f, backgroundColor.getAlpha()/255f);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
setupView(gl, glu);
@@ -315,8 +318,9 @@ public void run() {
}
pickPoint = null;
-
- gl.glClearColor(1, 1, 1, 1);
+
+ gl.glClearColor(backgroundColor.getRed()/255f, backgroundColor.getGreen()/255f,
+ backgroundColor.getBlue()/255f, backgroundColor.getAlpha()/255f);
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glEnable(GL.GL_MULTISAMPLE);
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java
index a97ea476fa..92cdb5b6c7 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/geometry/FinRenderer.java
@@ -16,6 +16,8 @@
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.gui.figure3d.geometry.Geometry.Surface;
+import java.util.Collections;
+
public class FinRenderer {
private GLUtessellator tess = GLU.gluNewTess();
@@ -96,12 +98,11 @@ public void combine(double[] coords, Object[] data, float[] weight, Object[] out
GLU.gluTessBeginPolygon(tess, null);
GLU.gluTessBeginContour(tess);
gl.glNormal3f(0, 0, 1);
- for (int i = tabPoints.length - 1; i >= 0; i--) {
- Coordinate c = tabPoints[i];
- double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
- c.z + finSet.getThickness() / 2.0};
- GLU.gluTessVertex(tess, p, 0, p);
- }
+ for (Coordinate c : tabPoints) {
+ double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
+ c.z + finSet.getThickness() / 2.0};
+ GLU.gluTessVertex(tess, p, 0, p);
+ }
GLU.gluTessEndContour(tess);
GLU.gluTessEndPolygon(tess);
}
@@ -125,7 +126,8 @@ public void combine(double[] coords, Object[] data, float[] weight, Object[] out
GLU.gluTessBeginPolygon(tess, null);
GLU.gluTessBeginContour(tess);
gl.glNormal3f(0, 0, -1);
- for (Coordinate c : tabPoints) {
+ for (int i = tabPoints.length - 1; i >= 0; i--) {
+ Coordinate c = tabPoints[i];
double[] p = new double[]{c.x, c.y + finSet.getBodyRadius(),
c.z - finSet.getThickness() / 2.0};
GLU.gluTessVertex(tess, p, 0, p);
@@ -163,7 +165,7 @@ public void combine(double[] coords, Object[] data, float[] weight, Object[] out
if (!(finSet instanceof EllipticalFinSet))
gl.glShadeModel(GLLightingFunc.GL_FLAT);
gl.glBegin(GL.GL_TRIANGLE_STRIP);
- for (int i = 0; i <= tabPoints.length; i++) {
+ for (int i = tabPoints.length; i >= 0; i--) {
Coordinate c = tabPoints[i % tabPoints.length];
// if ( i > 1 ){
Coordinate c2 = tabPoints[(i - 1 + tabPoints.length)
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java b/swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java
index 054ca02f31..d39eabcd88 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/geometry/TransitionRenderer.java
@@ -123,7 +123,7 @@ final class TransitionRenderer {
private TransitionRenderer() {
}
- static final void drawTransition(final GL2 gl, final Transition tr,
+ static void drawTransition(final GL2 gl, final Transition tr,
final int slices, final int stacks, final double offsetRadius) {
double da, r, dzBase;
@@ -131,14 +131,16 @@ static final void drawTransition(final GL2 gl, final Transition tr,
int i;
da = 2.0f * Math.PI / slices;
- dzBase = (double) tr.getLength() / stacks;
+ dzBase = tr.getLength() / stacks;
double ds = 1.0f / slices;
z = 0.0f;
- r = (double) tr.getForeRadius();
while (z < tr.getLength()) {
double t = z / tr.getLength();
+ if (tr.getForeRadius() > tr.getAftRadius()) {
+ t = 1 - t;
+ }
double dz = t < 0.025 ? dzBase / 8.0 : dzBase;
double zNext = Math.min(z + dz, tr.getLength());
diff --git a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoFrame.java b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoFrame.java
index 76af038230..d43c79ef60 100644
--- a/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoFrame.java
+++ b/swing/src/net/sf/openrocket/gui/figure3d/photo/PhotoFrame.java
@@ -371,9 +371,9 @@ public static void main(String args[]) throws Exception {
guiModule.startLoader();
- // Set the best available look-and-feel
- log.info("Setting best LAF");
- GUIUtil.setBestLAF();
+ // Set the look-and-feel
+ log.info("Setting LAF");
+ GUIUtil.applyLAF();
// Load defaults
((SwingPreferences) Application.getPreferences()).loadDefaultUnits();
diff --git a/swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java b/swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java
index 14a8a677d3..77361260ff 100644
--- a/swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java
+++ b/swing/src/net/sf/openrocket/gui/figureelements/CGCaret.java
@@ -1,5 +1,7 @@
package net.sf.openrocket.gui.figureelements;
+import net.sf.openrocket.gui.util.GUIUtil;
+
import java.awt.Color;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
@@ -16,7 +18,7 @@ public class CGCaret extends Caret {
private static final float RADIUS = 7;
private static Area caret = null;
-
+
/**
* Create a new CGCaret at the specified coordinates.
*/
@@ -56,7 +58,7 @@ protected Area getCaret() {
*/
@Override
protected Color getColor() {
- return Color.BLUE;
+ return GUIUtil.getUITheme().getCGColor();
}
}
diff --git a/swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java b/swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java
index 09e9cceba0..a5b66a2333 100644
--- a/swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java
+++ b/swing/src/net/sf/openrocket/gui/figureelements/CPCaret.java
@@ -1,5 +1,7 @@
package net.sf.openrocket.gui.figureelements;
+import net.sf.openrocket.gui.util.GUIUtil;
+
import java.awt.Color;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
@@ -15,7 +17,7 @@ public class CPCaret extends Caret {
private static final float RADIUS = 7;
private static Area caret = null;
-
+
/**
* Create a new CPCaret at the specified coordinates.
*/
@@ -51,6 +53,6 @@ protected Area getCaret() {
*/
@Override
protected Color getColor() {
- return Color.RED;
+ return GUIUtil.getUITheme().getCPColor();
}
}
diff --git a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
index 1af88b65b5..1836ae6442 100644
--- a/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
+++ b/swing/src/net/sf/openrocket/gui/figureelements/RocketInfo.java
@@ -3,7 +3,6 @@
import static net.sf.openrocket.util.Chars.ALPHA;
import static net.sf.openrocket.util.Chars.THETA;
-import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
@@ -11,6 +10,7 @@
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.logging.Warning;
import net.sf.openrocket.logging.WarningSet;
@@ -176,7 +176,7 @@ private void drawMainInfo() {
GlyphVector massLineWithoutMotors = createText(massTextWithoutMotors);
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getTextColor());
g2.drawGlyphVector(name, x1, y1);
g2.drawGlyphVector(lengthLine, x1, y1+line);
@@ -234,7 +234,7 @@ private void drawStabilityInfo() {
unitWidth = unitWidth + spaceWidth;
stabUnitWidth = stabUnitWidth + spaceWidth;
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getTextColor());
// Draw the stability, CG & CP values (and units)
g2.drawGlyphVector(stabValue, (float)(x2-stabRect.getWidth()), y1);
@@ -261,7 +261,7 @@ private void drawStabilityInfo() {
atPos = (float)(x2 - atTextRect.getWidth());
}
- g2.setColor(Color.GRAY);
+ g2.setColor(GUIUtil.getUITheme().getDimTextColor());
g2.drawGlyphVector(atText, atPos, y1 + 3*line);
}
@@ -411,7 +411,7 @@ private void drawWarnings() {
float y = y2 - line * (texts.length-1);
- g2.setColor(Color.RED);
+ g2.setColor(GUIUtil.getUITheme().getWarningColor());
for (GlyphVector v: texts) {
Rectangle2D rect = v.getVisualBounds();
@@ -427,7 +427,7 @@ private void drawFlightInformation() {
if (calculatingData) {
//// Calculating...
GlyphVector calculating = createText(trans.get("RocketInfo.Calculating"));
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getTextColor());
g2.drawGlyphVector(calculating, x1, (float)(y2-height));
}
}
@@ -485,11 +485,10 @@ private double drawFlightData() {
width += 5;
if (!calculatingData)
- g2.setColor(new Color(0,0,127));
+ g2.setColor(GUIUtil.getUITheme().getFlightDataTextActiveColor());
else
- g2.setColor(new Color(0,0,127,127));
+ g2.setColor(GUIUtil.getUITheme().getFlightDataTextInactiveColor());
-
g2.drawGlyphVector(apogee, (float)x1, (float)(y2-2*line));
g2.drawGlyphVector(maxVelocity, (float)x1, (float)(y2-line));
g2.drawGlyphVector(maxAcceleration, (float)x1, (float)(y2));
@@ -502,7 +501,7 @@ private double drawFlightData() {
}
private synchronized void updateFontSizes() {
- float size = Application.getPreferences().getRocketInfoFontSize();
+ float size = ((SwingPreferences) Application.getPreferences()).getRocketInfoFontSize();
// No change necessary as the font is the same size, just use the existing version
if (font.getSize2D() == size) {
return;
diff --git a/swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java
index 0d9e38155c..7828dfd8db 100644
--- a/swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java
+++ b/swing/src/net/sf/openrocket/gui/help/tours/SlideSetManager.java
@@ -1,5 +1,6 @@
package net.sf.openrocket.gui.help.tours;
+import java.awt.Color;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -11,6 +12,7 @@
import javax.swing.text.html.StyleSheet;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.util.BugException;
/**
@@ -130,6 +132,9 @@ private StyleSheet loadStyleSheet() throws IOException {
try {
StyleSheet ss = new StyleSheet();
+ Color textColor = GUIUtil.getUITheme().getTextColor();
+ ss.addRule(String.format("p { color: rgb(%d, %d, %d, %d)",
+ textColor.getRed(), textColor.getGreen(), textColor.getBlue(), textColor.getAlpha()));
InputStreamReader reader = new InputStreamReader(in, "UTF-8");
ss.loadRules(reader, null);
return ss;
diff --git a/swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java
index 07cffdcc69..561ee518b9 100644
--- a/swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java
+++ b/swing/src/net/sf/openrocket/gui/help/tours/SlideShowComponent.java
@@ -12,6 +12,7 @@
import javax.swing.text.html.StyleSheet;
import net.sf.openrocket.gui.components.ImageDisplayComponent;
+import net.sf.openrocket.gui.util.GUIUtil;
/**
* Component that displays a single slide, with the image on top and
@@ -28,7 +29,7 @@ public class SlideShowComponent extends JSplitPane {
private final ImageDisplayComponent imageDisplay;
private final JEditorPane textPane;
-
+
public SlideShowComponent() {
super(VERTICAL_SPLIT);
@@ -44,6 +45,7 @@ public SlideShowComponent() {
textPane.setPreferredSize(new Dimension(WIDTH, HEIGHT_TEXT));
JScrollPane scrollPanel = new JScrollPane(textPane);
+ textPane.setBorder(GUIUtil.getUITheme().getBorder());
this.setRightComponent(scrollPanel);
this.setResizeWeight(((double) HEIGHT_IMAGE) / (HEIGHT_IMAGE + HEIGHT_TEXT));
diff --git a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
index 194dac8214..a216f7d3cc 100644
--- a/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
+++ b/swing/src/net/sf/openrocket/gui/main/BasicFrame.java
@@ -579,12 +579,12 @@ public void actionPerformed(ActionEvent e) {
editMenu.addSeparator();
- JMenu subMenu = new JMenu(trans.get("RocketActions.Select"));
- editMenu.add(subMenu);
+ JMenu selectSubMenu = new JMenu(trans.get("RocketActions.Select"));
+ editMenu.add(selectSubMenu);
item = new JMenuItem(actions.getSelectSameColorAction());
- subMenu.add(item);
+ selectSubMenu.add(item);
item = new JMenuItem(actions.getDeselectAllAction());
- subMenu.add(item);
+ selectSubMenu.add(item);
editMenu.addSeparator();
diff --git a/swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java b/swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java
index 69869526c6..1f194ce05c 100644
--- a/swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java
+++ b/swing/src/net/sf/openrocket/gui/main/ComponentAddButtons.java
@@ -4,6 +4,7 @@
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
+import java.awt.Font;
import java.awt.event.ActionEvent;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -209,7 +210,7 @@ public ComponentAddButtons(OpenRocketDocument document, TreeSelectionModel model
for (col = 0; col < buttons[row].length; col++) {
buttons[row][col].setMinimumSize(d);
buttons[row][col].setPreferredSize(d);
- buttons[row][col].getComponent(0).validate();
+ buttons[row][col].validate();
}
}
@@ -315,29 +316,32 @@ public ComponentButton(String text) {
* The label may contain "\n" as a newline.
*/
public ComponentButton(String text, Icon enabled, Icon disabled) {
- super();
- setLayout(new MigLayout("fill, flowy, insets 0, gap 0", "", ""));
-
- add(new JLabel(), "push, sizegroup spacing");
-
- // Add Icon
- if (enabled != null) {
- JLabel label = new JLabel(enabled);
- if (disabled != null)
- label.setDisabledIcon(disabled);
- add(label, "growx");
+ super(text, enabled);
+
+ setVerticalTextPosition(SwingConstants.BOTTOM); // this will put the text below the icon
+ setHorizontalTextPosition(SwingConstants.CENTER); // this will center the text horizontally beneath the icon
+ //setIconTextGap(0); // this is optional, it sets the gap between the icon and the text
+
+ // set the disabled icon if it is not null
+ if (disabled != null) {
+ setDisabledIcon(disabled);
}
-
- // Add labels
- String[] l = text.split("\n");
- for (int i = 0; i < l.length; i++) {
- add(new StyledLabel(l[i], SwingConstants.CENTER, -2.0f), "growx");
+
+ setHorizontalAlignment(SwingConstants.CENTER); // this will center the button itself in its parent component
+
+ // if you have multiline text, you could use html to format it
+ if (text != null && text.contains("\n")) {
+ text = "" + text.replace("\n", "
") + "";
+ setText(text);
+ }
+
+ // Initialize enabled status
+ valueChanged(null);
+
+ // Attach a tree selection listener if selection model is not null
+ if (selectionModel != null) {
+ selectionModel.addTreeSelectionListener(this);
}
-
- add(new JLabel(), "push, sizegroup spacing");
-
- valueChanged(null); // Update enabled status
- selectionModel.addTreeSelectionListener(this);
}
diff --git a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java
index b30a605ce3..f4e7c274dc 100644
--- a/swing/src/net/sf/openrocket/gui/main/DesignPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/DesignPanel.java
@@ -61,13 +61,13 @@ public DesignPanel(final BasicFrame parent, final OpenRocketDocument document, f
// Remove JTree key events that interfere with menu accelerators
InputMap im = SwingUtilities.getUIInputMap(tree, JComponent.WHEN_FOCUSED);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_KEY), null);
- im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, SHORTCUT_KEY), null);
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_S, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, SHORTCUT_KEY), "none");
// Highlight all child components of a stage/rocket/podset when it is selected
tree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
@@ -174,6 +174,7 @@ public void valueChanged(TreeSelectionEvent e) {
// Place tree inside scroll pane
JScrollPane scroll = new JScrollPane(tree);
+ tree.setBorder(GUIUtil.getUITheme().getBorder());
panel.add(scroll, "spany, wmin 140px, grow, wrap");
diff --git a/swing/src/net/sf/openrocket/gui/main/RocketActions.java b/swing/src/net/sf/openrocket/gui/main/RocketActions.java
index 2bfa6e9a65..aa90f9eafc 100644
--- a/swing/src/net/sf/openrocket/gui/main/RocketActions.java
+++ b/swing/src/net/sf/openrocket/gui/main/RocketActions.java
@@ -4,6 +4,7 @@
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
+import java.io.Serial;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -13,15 +14,13 @@
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
-import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
-import javax.swing.JPanel;
import javax.swing.KeyStroke;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
-import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
-import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.configdialog.ComponentConfigDialog;
import net.sf.openrocket.gui.dialogs.ScaleDialog;
import net.sf.openrocket.gui.util.Icons;
@@ -34,7 +33,6 @@
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.startup.Application;
-import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.util.Color;
import net.sf.openrocket.util.Pair;
import org.slf4j.Logger;
@@ -59,6 +57,7 @@ public class RocketActions {
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx());
public static final KeyStroke EDIT_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_E,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx());
+ public static final KeyStroke DELETE_KEY_STROKE = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
private final OpenRocketDocument document;
private final Rocket rocket;
@@ -67,8 +66,6 @@ public class RocketActions {
private final SimulationPanel simulationPanel;
- private final RocketAction deleteComponentAction;
- private final RocketAction deleteSimulationAction;
private final RocketAction deleteAction;
private final RocketAction cutAction;
private final RocketAction copyAction;
@@ -94,8 +91,6 @@ public RocketActions(OpenRocketDocument document, DocumentSelectionModel selecti
// Add action also to updateActions()
this.deleteAction = new DeleteAction();
- this.deleteComponentAction = new DeleteComponentAction();
- this.deleteSimulationAction = new DeleteSimulationAction();
this.cutAction = new CutAction();
this.copyAction = new CopyAction();
this.pasteAction = new PasteAction();
@@ -107,10 +102,15 @@ public RocketActions(OpenRocketDocument document, DocumentSelectionModel selecti
this.moveUpAction = new MoveUpAction();
this.moveDownAction = new MoveDownAction();
- OpenRocketClipboard.addClipboardListener(this.pasteAction);
+ OpenRocketClipboard.addClipboardListener(new ClipboardListener() {
+ @Override
+ public void clipboardChanged() {
+ updateActions();
+ }
+ });
updateActions();
- // Update all actions when tree selection or rocket changes
+ // Update all actions when tree selection, simulation selection or rocket changes
selectionModel.addDocumentSelectionListener(new DocumentSelectionListener() {
@Override
@@ -118,6 +118,12 @@ public void valueChanged(int changeType) {
updateActions();
}
});
+ simulationPanel.addSimulationTableListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ updateActions();
+ }
+ });
document.getRocket().addComponentChangeListener(new ComponentChangeListener() {
@Override
public void componentChanged(ComponentChangeEvent e) {
@@ -130,12 +136,13 @@ public void componentChanged(ComponentChangeEvent e) {
* Update the state of all of the actions.
*/
private void updateActions() {
- deleteAction.clipboardChanged();
+ simulationPanel.updateActions();
+ editAction.clipboardChanged();
cutAction.clipboardChanged();
copyAction.clipboardChanged();
pasteAction.clipboardChanged();
+ deleteAction.clipboardChanged();
duplicateAction.clipboardChanged();
- editAction.clipboardChanged();
selectSameColorAction.clipboardChanged();
deselectAllAction.clipboardChanged();
scaleAction.clipboardChanged();
@@ -143,16 +150,8 @@ private void updateActions() {
moveDownAction.clipboardChanged();
}
-
-
- public Action getDeleteComponentAction() {
- return deleteAction;
- }
- public Action getDeleteSimulationAction() {
- return deleteAction;
- }
public Action getDeleteAction() {
return deleteAction;
@@ -313,7 +312,7 @@ private boolean isCopyable(RocketComponent c) {
}
private boolean isCopyable(List components) {
- if (components == null || components.size() == 0) return false;
+ if (components == null || components.isEmpty()) return false;
for (RocketComponent component : components) {
if (!isCopyable(component)) return false;
}
@@ -409,43 +408,14 @@ private void fillInMissingSelections(List selections) {
private boolean isSimulationSelected() {
Simulation[] selection = selectionModel.getSelectedSimulations();
- return (selection != null && selection.length > 0);
- }
-
-
-
- private boolean verifyDeleteSimulation() {
- boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
- if (verify) {
- JPanel panel = new JPanel(new MigLayout());
- //// Do not ask me again
- JCheckBox dontAsk = new JCheckBox(trans.get("RocketActions.checkbox.Donotaskmeagain"));
- panel.add(dontAsk,"wrap");
- //// You can change the default operation in the preferences.
- panel.add(new StyledLabel(trans.get("RocketActions.lbl.Youcanchangedefop"),-2));
-
- int ret = JOptionPane.showConfirmDialog(
- parentFrame,
- new Object[] {
- //// Delete the selected simulations?
- trans.get("RocketActions.showConfirmDialog.lbl1"),
- //// This operation cannot be undone.
- trans.get("RocketActions.showConfirmDialog.lbl2"),
- "",
- panel },
- //// Delete simulations
- trans.get("RocketActions.showConfirmDialog.title"),
- JOptionPane.OK_CANCEL_OPTION,
- JOptionPane.WARNING_MESSAGE);
- if (ret != JOptionPane.OK_OPTION)
- return false;
-
- if (dontAsk.isSelected()) {
- Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
+ if (selection != null && selection.length > 0) {
+ List components = selectionModel.getSelectedComponents();
+ if (components != null && !components.isEmpty()) {
+ throw new IllegalStateException("Both simulation and component selected");
}
+ return true;
}
-
- return true;
+ return false;
}
@@ -517,130 +487,60 @@ private abstract class RocketAction extends AbstractAction implements ClipboardL
public abstract void clipboardChanged();
}
-
/**
- * Action that deletes the selected component.
+ * Action to edit the currently selected component.
*/
- private class DeleteComponentAction extends RocketAction {
+ private class EditAction extends RocketAction {
+ @Serial
private static final long serialVersionUID = 1L;
- public DeleteComponentAction() {
- //// Delete
- this.putValue(NAME, trans.get("RocketActions.DelCompAct.Delete"));
- //// Delete the selected component.
- this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelCompAct.ttip.Delete"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
-// this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
- this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
+ public EditAction() {
+ //// Edit
+ this.putValue(NAME, trans.get("RocketActions.EditAct.Edit"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_E);
+ this.putValue(ACCELERATOR_KEY, EDIT_KEY_STROKE);
+ this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.EditAct.ttip.Edit"));
+ this.putValue(SMALL_ICON, Icons.EDIT_EDIT);
clipboardChanged();
}
@Override
public void actionPerformed(ActionEvent e) {
- List components = new ArrayList<>(selectionModel.getSelectedComponents());
- if (components.size() == 0) return;
- components.sort(Comparator.comparing(c -> c.getParent().getChildPosition(c)));
-
- if (components.size() == 1) {
- document.addUndoPosition("Delete " + components.get(0).getComponentName());
+ if (isSimulationSelected()) {
+ simulationPanel.getEditSimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
} else {
- document.addUndoPosition("Delete components");
- }
-
- for (RocketComponent component : components) {
- deleteComponent(component);
+ editComponents();
+ // I wouldn't switch to the design tab here, because the user may be editing in the rocket view window
}
}
- private void deleteComponent(RocketComponent component) {
- if (isDeletable(component)) {
- ComponentConfigDialog.disposeDialog();
-
- try {
- component.getRocket().removeComponentChangeListener(ComponentConfigDialog.getDialog());
+ private void editComponents() {
+ List components = selectionModel.getSelectedComponents();
- delete(component);
- } catch (IllegalStateException ignored) { }
+ if (components == null || components.isEmpty()) {
+ return;
}
- }
-
- @Override
- public void clipboardChanged() {
- this.setEnabled(isDeletable(selectionModel.getSelectedComponent()));
- }
- }
-
-
- /**
- * Action that deletes the selected component.
- */
- private class DeleteSimulationAction extends RocketAction {
- private static final long serialVersionUID = 1L;
-
- public DeleteSimulationAction() {
- //// Delete
- this.putValue(NAME, trans.get("RocketActions.DelSimuAct.Delete"));
- //// Delete the selected simulation.
- this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelSimuAct.ttip.Delete"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
-// this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
- this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
- clipboardChanged();
- }
+ if (ComponentConfigDialog.isDialogVisible())
+ ComponentConfigDialog.disposeDialog();
- @Override
- public void actionPerformed(ActionEvent e) {
- Simulation[] sims = selectionModel.getSelectedSimulations();
- if (sims.length > 0) {
- if (verifyDeleteSimulation()) {
- for (Simulation s: sims) {
- document.removeSimulation(s);
- }
+ RocketComponent component = components.get(0);
+ component.clearConfigListeners();
+ if (components.size() > 1) {
+ for (int i = 1; i < components.size(); i++) {
+ RocketComponent listener = components.get(i);
+ listener.clearConfigListeners(); // Make sure all the listeners are cleared (should not be possible, but just in case)
+ component.addConfigListener(listener);
}
}
+ ComponentConfigDialog.showDialog(parentFrame, document, component);
}
@Override
public void clipboardChanged() {
- this.setEnabled(isSimulationSelected());
- }
- }
-
-
-
- /**
- * Action that deletes the selected component.
- */
- private class DeleteAction extends RocketAction {
- private static final long serialVersionUID = 1L;
-
- public DeleteAction() {
- //// Delete
- this.putValue(NAME, trans.get("RocketActions.DelAct.Delete"));
- //// Delete the selected component or simulation.
- this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelAct.ttip.Delete"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
- this.putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
- this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
- clipboardChanged();
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- if (isSimulationSelected()) {
- deleteSimulationAction.actionPerformed(e);
- parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
- } else {
- deleteComponentAction.actionPerformed(e);
- parentFrame.selectTab(BasicFrame.DESIGN_TAB);
- }
- }
-
- @Override
- public void clipboardChanged() {
- this.setEnabled(isDeletable(selectionModel.getSelectedComponent()) ||
- isSimulationSelected());
+ List components = selectionModel.getSelectedComponents();
+ this.setEnabled(!components.isEmpty() || simulationPanel.getEditSimulationAction().isEnabled());
}
}
@@ -650,14 +550,14 @@ public void clipboardChanged() {
* Action the cuts the selected component (copies to clipboard and deletes).
*/
private class CutAction extends RocketAction {
+ @Serial
private static final long serialVersionUID = 1L;
public CutAction() {
//// Cut
this.putValue(NAME, trans.get("RocketActions.CutAction.Cut"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_T);
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_T); // Use the 't' in Cut as mnemonic
this.putValue(ACCELERATOR_KEY, CUT_KEY_STROKE);
- //// Cut this component or simulation to the clipboard and remove from this design
this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.CutAction.ttip.Cut"));
this.putValue(SMALL_ICON, Icons.EDIT_CUT);
clipboardChanged();
@@ -665,49 +565,49 @@ public CutAction() {
@Override
public void actionPerformed(ActionEvent e) {
+ if (isSimulationSelected()) {
+ simulationPanel.getCutSimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ } else {
+ cutComponents();
+ }
+ }
+
+ private void cutComponents() {
List components = selectionModel.getSelectedComponents();
- if (components.size() > 0) {
+ if (!components.isEmpty()) {
components = new ArrayList<>(components);
fillInMissingSelections(components);
components.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
}
- Simulation[] sims = selectionModel.getSelectedSimulations();
- if (isDeletable(components) && isCopyable(components)) {
- ComponentConfigDialog.disposeDialog();
+ if (!(isDeletable(components) && isCopyable(components))) {
+ return;
+ }
- if (components.size() == 1) {
- document.addUndoPosition("Cut " + components.get(0).getComponentName());
- } else {
- document.addUndoPosition("Cut components");
- }
+ ComponentConfigDialog.disposeDialog();
- List copiedComponents = new LinkedList<>(copyComponentsMaintainParent(components));
- copiedComponents.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
+ if (components.size() == 1) {
+ document.addUndoPosition("Cut " + components.get(0).getComponentName());
+ } else {
+ document.addUndoPosition("Cut components");
+ }
- OpenRocketClipboard.setClipboard(copiedComponents);
- delete(components);
- parentFrame.selectTab(BasicFrame.DESIGN_TAB);
- } else if (isSimulationSelected()) {
+ List copiedComponents = new LinkedList<>(copyComponentsMaintainParent(components));
+ copiedComponents.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
- Simulation[] simsCopy = new Simulation[sims.length];
- for (int i=0; i < sims.length; i++) {
- simsCopy[i] = sims[i].copy();
- }
- OpenRocketClipboard.setClipboard(simsCopy);
+ OpenRocketClipboard.setClipboard(copiedComponents);
+ delete(components);
- for (Simulation s: sims) {
- document.removeSimulation(s);
- }
- parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
- }
+ parentFrame.selectTab(BasicFrame.DESIGN_TAB);
}
@Override
public void clipboardChanged() {
List components = selectionModel.getSelectedComponents();
- this.setEnabled((isDeletable(components) && isCopyable(components)) || isSimulationSelected());
+ this.setEnabled((isDeletable(components) && isCopyable(components)) ||
+ simulationPanel.getCutSimulationAction().isEnabled());
}
}
@@ -722,46 +622,47 @@ private class CopyAction extends RocketAction {
public CopyAction() {
//// Copy
this.putValue(NAME, trans.get("RocketActions.CopyAct.Copy"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.CopyAct.ttip.Copy"));
this.putValue(MNEMONIC_KEY, KeyEvent.VK_C);
this.putValue(ACCELERATOR_KEY, COPY_KEY_STROKE);
- //// Copy this component (and subcomponents) to the clipboard.
- this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.CopyAct.ttip.Copy"));
this.putValue(SMALL_ICON, Icons.EDIT_COPY);
clipboardChanged();
}
@Override
public void actionPerformed(ActionEvent e) {
+ if (isSimulationSelected()) {
+ simulationPanel.getCopySimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ } else {
+ copyComponents();
+ }
+ }
+
+ private void copyComponents() {
List components = selectionModel.getSelectedComponents();
- if (components.size() > 0) {
+ if (!components.isEmpty()) {
components = new ArrayList<>(components);
fillInMissingSelections(components);
components.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
}
- Simulation[] sims = selectionModel.getSelectedSimulations();
-
- if (isCopyable(components)) {
- List copiedComponents = new LinkedList<>(copyComponentsMaintainParent(components));
- copiedComponents.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
-
- OpenRocketClipboard.setClipboard(copiedComponents);
- parentFrame.selectTab(BasicFrame.DESIGN_TAB);
- } else if (sims != null && sims.length > 0) {
- Simulation[] simsCopy = new Simulation[sims.length];
- for (int i=0; i < sims.length; i++) {
- simsCopy[i] = sims[i].copy();
- }
- OpenRocketClipboard.setClipboard(simsCopy);
- parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ if (!isCopyable(components)) {
+ return;
}
+
+ List copiedComponents = new LinkedList<>(copyComponentsMaintainParent(components));
+ copiedComponents.sort(Comparator.comparing(c -> c.getParent() != null ? -c.getParent().getChildPosition(c) : 0));
+
+ OpenRocketClipboard.setClipboard(copiedComponents);
+ parentFrame.selectTab(BasicFrame.DESIGN_TAB);
}
@Override
public void clipboardChanged() {
this.setEnabled(isCopyable(selectionModel.getSelectedComponent()) ||
- isSimulationSelected());
+ simulationPanel.getCopySimulationAction().isEnabled());
}
}
@@ -779,9 +680,8 @@ private class PasteAction extends RocketAction {
public PasteAction() {
//// Paste
this.putValue(NAME, trans.get("RocketActions.PasteAct.Paste"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_P);
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_V);
this.putValue(ACCELERATOR_KEY, PASTE_KEY_STROKE);
- //// Paste the component or simulation on the clipboard to the design.
this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.PasteAct.ttip.Paste"));
this.putValue(SMALL_ICON, Icons.EDIT_PASTE);
clipboardChanged();
@@ -789,76 +689,67 @@ public PasteAction() {
@Override
public void actionPerformed(ActionEvent e) {
+ if (isSimulationSelected()) {
+ simulationPanel.getPasteSimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ } else {
+ pasteComponents();
+ }
+ }
+
+ private void pasteComponents() {
List components = new LinkedList<>(OpenRocketClipboard.getClipboardComponents());
- Simulation[] sims = OpenRocketClipboard.getClipboardSimulations();
- if (components.size() > 0) {
- ComponentConfigDialog.disposeDialog();
-
- List pasted = new LinkedList<>();
- for (RocketComponent component : components) {
- pasted.add(component.copy());
- }
+ if (components.isEmpty()) {
+ return;
+ }
+ ComponentConfigDialog.disposeDialog();
+
+ List pasted = new LinkedList<>();
+ for (RocketComponent component : components) {
+ pasted.add(component.copy());
+ }
- List> positions = getPastePositions(pasted);
+ List> positions = getPastePositions(pasted);
- if (pasted.size() == 1) {
- document.addUndoPosition("Paste " + pasted.get(0).getComponentName());
- } else {
- document.addUndoPosition("Paste components");
- }
+ if (pasted.size() == 1) {
+ document.addUndoPosition("Paste " + pasted.get(0).getComponentName());
+ } else {
+ document.addUndoPosition("Paste components");
+ }
- List successfullyPasted = new LinkedList<>();
- for (int i = 0; i < pasted.size(); i++) {
- if (positions.get(i) == null) {
- JOptionPane.showMessageDialog(null,
- String.format(trans.get("RocketActions.PasteAct.invalidPosition.msg"),
- pasted.get(i).getComponentName()),
- trans.get("RocketActions.PasteAct.invalidPosition.title"), JOptionPane.WARNING_MESSAGE);
+ List successfullyPasted = new LinkedList<>();
+ for (int i = 0; i < pasted.size(); i++) {
+ if (positions.get(i) == null) {
+ JOptionPane.showMessageDialog(null,
+ String.format(trans.get("RocketActions.PasteAct.invalidPosition.msg"),
+ pasted.get(i).getComponentName()),
+ trans.get("RocketActions.PasteAct.invalidPosition.title"), JOptionPane.WARNING_MESSAGE);
+ } else {
+ RocketComponent parent = positions.get(i).getU();
+ RocketComponent child = pasted.get(i);
+ if (parent != null && parent.isCompatible(child)) {
+ parent.addChild(child, positions.get(i).getV());
+ successfullyPasted.add(pasted.get(i));
} else {
- RocketComponent parent = positions.get(i).getU();
- RocketComponent child = pasted.get(i);
- if (parent != null && parent.isCompatible(child)) {
- parent.addChild(child, positions.get(i).getV());
- successfullyPasted.add(pasted.get(i));
- } else {
- log.warn("Pasted component {} is not compatible with {}", child, parent);
- }
- }
- }
-
- selectionModel.setSelectedComponents(successfullyPasted);
-
- parentFrame.selectTab(BasicFrame.DESIGN_TAB);
-
- } else if (sims != null) {
-
- ArrayList copySims = new ArrayList();
-
- for (Simulation s: sims) {
- Simulation copy = s.duplicateSimulation(rocket);
- String name = copy.getName();
- if (name.matches(OpenRocketDocument.SIMULATION_NAME_PREFIX + "[0-9]+ *")) {
- copy.setName(document.getNextSimulationName());
+ log.warn("Pasted component {} is not compatible with {}", child, parent);
}
- document.addSimulation(copy);
- copySims.add(copy);
}
- selectionModel.setSelectedSimulations(copySims.toArray(new Simulation[0]));
-
- parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
}
+
+ selectionModel.setSelectedComponents(successfullyPasted);
+ parentFrame.selectTab(BasicFrame.DESIGN_TAB);
}
@Override
public void clipboardChanged() {
- this.setEnabled(
- (getPastePositions(OpenRocketClipboard.getClipboardComponents()).size() > 0) ||
- (OpenRocketClipboard.getClipboardSimulations() != null));
+ this.setEnabled(!getPastePositions(OpenRocketClipboard.getClipboardComponents()).isEmpty() ||
+ simulationPanel.getPasteSimulationAction().isEnabled());
}
}
+
/**
* Action that duplicates the selected component.
*/
@@ -870,7 +761,6 @@ public DuplicateAction() {
this.putValue(NAME, trans.get("RocketActions.DuplicateAct.Duplicate"));
this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
this.putValue(ACCELERATOR_KEY, DUPLICATE_KEY_STROKE);
- //// Copy this component (and subcomponents) to the clipboard.
this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DuplicateAct.ttip.Duplicate"));
this.putValue(SMALL_ICON, Icons.EDIT_DUPLICATE);
clipboardChanged();
@@ -878,6 +768,15 @@ public DuplicateAction() {
@Override
public void actionPerformed(ActionEvent e) {
+ if (isSimulationSelected()) {
+ simulationPanel.getDuplicateSimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ } else {
+ duplicateComponents();
+ }
+ }
+
+ private void duplicateComponents() {
List components = selectionModel.getSelectedComponents();
List topComponents = new LinkedList<>(); // Components without a parent component in
if (components.size() > 0) {
@@ -892,136 +791,130 @@ public void actionPerformed(ActionEvent e) {
topComponents.add(c);
}
}
- Simulation[] sims = selectionModel.getSelectedSimulations();
-
- if (isCopyable(components)) {
- if (ComponentConfigDialog.isDialogVisible())
- ComponentConfigDialog.disposeDialog();
-
- List copiedComponents = copyComponentsMaintainParent(components);
- OpenRocketClipboard.setClipboard(copiedComponents);
- copiedComponents = new LinkedList<>(OpenRocketClipboard.getClipboardComponents());
-
- List duplicateComponents = new LinkedList<>();
- for (RocketComponent component : copiedComponents) {
- duplicateComponents.add(component.copy());
- }
-
- List> positions = new LinkedList<>();
- for (RocketComponent component : duplicateComponents) {
- Pair pos;
- if (RocketComponent.listContainsParent(duplicateComponents, component)) {
- pos = getPastePosition(component, component.getParent());
- } else {
- int compIdx = duplicateComponents.indexOf(component);
- RocketComponent pasteParent = topComponents.get(compIdx).getParent();
- pos = getPastePosition(component, pasteParent);
- }
- positions.add(pos);
- }
- if (duplicateComponents.size() == 1) {
- document.addUndoPosition("Duplicate " + duplicateComponents.get(0).getComponentName());
- } else {
- document.addUndoPosition("Duplicate components");
- }
-
- Collections.reverse(duplicateComponents);
- Collections.reverse(positions);
+ if (!isCopyable(components)) {
+ return;
+ }
- for (int i = 0; i < duplicateComponents.size(); i++) {
- positions.get(i).getU().addChild(duplicateComponents.get(i), positions.get(i).getV());
- }
+ if (ComponentConfigDialog.isDialogVisible())
+ ComponentConfigDialog.disposeDialog();
- selectionModel.setSelectedComponents(duplicateComponents);
+ List copiedComponents = copyComponentsMaintainParent(components);
+ OpenRocketClipboard.setClipboard(copiedComponents);
+ copiedComponents = new LinkedList<>(OpenRocketClipboard.getClipboardComponents());
- parentFrame.selectTab(BasicFrame.DESIGN_TAB);
- } else if (sims != null && sims.length > 0) {
- ArrayList copySims = new ArrayList();
+ List duplicateComponents = new LinkedList<>();
+ for (RocketComponent component : copiedComponents) {
+ duplicateComponents.add(component.copy());
+ }
- // TODO: the undoing doesn't do anything...
- if (sims.length == 1) {
- document.addUndoPosition("Duplicate " + sims[0].getName());
+ List> positions = new LinkedList<>();
+ for (RocketComponent component : duplicateComponents) {
+ Pair pos;
+ if (RocketComponent.listContainsParent(duplicateComponents, component)) {
+ pos = getPastePosition(component, component.getParent());
} else {
- document.addUndoPosition("Duplicate simulations");
+ int compIdx = duplicateComponents.indexOf(component);
+ RocketComponent pasteParent = topComponents.get(compIdx).getParent();
+ pos = getPastePosition(component, pasteParent);
}
+ positions.add(pos);
+ }
- for (Simulation s: sims) {
- Simulation copy = s.duplicateSimulation(rocket);
- String name = copy.getName();
- if (name.matches(OpenRocketDocument.SIMULATION_NAME_PREFIX + "[0-9]+ *")) {
- copy.setName(document.getNextSimulationName());
- }
- document.addSimulation(copy);
- copySims.add(copy);
- }
+ if (duplicateComponents.size() == 1) {
+ document.addUndoPosition("Duplicate " + duplicateComponents.get(0).getComponentName());
+ } else {
+ document.addUndoPosition("Duplicate components");
+ }
- selectionModel.setSelectedSimulations(copySims.toArray(new Simulation[0]));
+ Collections.reverse(duplicateComponents);
+ Collections.reverse(positions);
- parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ for (int i = 0; i < duplicateComponents.size(); i++) {
+ positions.get(i).getU().addChild(duplicateComponents.get(i), positions.get(i).getV());
}
+
+ selectionModel.setSelectedComponents(duplicateComponents);
+ parentFrame.selectTab(BasicFrame.DESIGN_TAB);
}
@Override
public void clipboardChanged() {
this.setEnabled(isCopyable(selectionModel.getSelectedComponent()) ||
- isSimulationSelected());
+ simulationPanel.getDuplicateSimulationAction().isEnabled());
}
}
-
-
+
+
/**
- * Action to edit the currently selected component.
+ * Action that deletes the selected component.
*/
- private class EditAction extends RocketAction {
+ private class DeleteAction extends RocketAction {
private static final long serialVersionUID = 1L;
- public EditAction() {
- //// Edit
- this.putValue(NAME, trans.get("RocketActions.EditAct.Edit"));
- this.putValue(MNEMONIC_KEY, KeyEvent.VK_E);
- this.putValue(ACCELERATOR_KEY, EDIT_KEY_STROKE);
- //// Edit the selected component.
- this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.EditAct.ttip.Edit"));
- this.putValue(SMALL_ICON, Icons.EDIT_EDIT);
+ public DeleteAction() {
+ //// Delete
+ this.putValue(NAME, trans.get("RocketActions.DelAct.Delete"));
+ //// Delete the selected component or simulation.
+ this.putValue(SHORT_DESCRIPTION, trans.get("RocketActions.DelAct.ttip.Delete"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_DELETE);
+ this.putValue(ACCELERATOR_KEY, DELETE_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
clipboardChanged();
}
@Override
public void actionPerformed(ActionEvent e) {
- List components = selectionModel.getSelectedComponents();
- Simulation[] sims = selectionModel.getSelectedSimulations();
+ if (isSimulationSelected()) {
+ simulationPanel.getDeleteSimulationAction().actionPerformed(e);
+ parentFrame.selectTab(BasicFrame.SIMULATION_TAB);
+ } else {
+ deleteComponents();
+ }
+ }
- if (components.size() > 0) {
- if (ComponentConfigDialog.isDialogVisible())
- ComponentConfigDialog.disposeDialog();
-
- RocketComponent component = components.get(0);
- component.clearConfigListeners();
- if (components.size() > 1) {
- for (int i = 1; i < components.size(); i++) {
- RocketComponent listener = components.get(i);
- listener.clearConfigListeners(); // Make sure all the listeners are cleared (should not be possible, but just in case)
- component.addConfigListener(listener);
- }
- }
- ComponentConfigDialog.showDialog(parentFrame, document, component);
- } else if (sims != null && sims.length > 0 && (simulationPanel != null)) {
- simulationPanel.editSimulation();
+ private void deleteComponents() {
+ List components = new ArrayList<>(selectionModel.getSelectedComponents());
+ if (components.size() == 0) return;
+ components.sort(Comparator.comparing(c -> c.getParent().getChildPosition(c)));
+
+ if (components.size() == 1) {
+ document.addUndoPosition("Delete " + components.get(0).getComponentName());
+ } else {
+ document.addUndoPosition("Delete components");
+ }
+
+ for (RocketComponent component : components) {
+ deleteComponent(component);
+ }
+
+ parentFrame.selectTab(BasicFrame.DESIGN_TAB);
+ }
+
+ private void deleteComponent(RocketComponent component) {
+ if (isDeletable(component)) {
+ ComponentConfigDialog.disposeDialog();
+
+ try {
+ component.getRocket().removeComponentChangeListener(ComponentConfigDialog.getDialog());
+
+ delete(component);
+ } catch (IllegalStateException ignored) { }
}
}
@Override
public void clipboardChanged() {
- List components = selectionModel.getSelectedComponents();
-
- this.setEnabled(components.size() > 0 || isSimulationSelected());
+ this.setEnabled(isDeletable(selectionModel.getSelectedComponent()) ||
+ simulationPanel.getDeleteSimulationAction().isEnabled());
}
}
+
+
/**
* Action to select all components with the same color as the currently selected component.
*/
@@ -1038,7 +931,7 @@ public SelectSameColorAction() {
@Override
public void actionPerformed(ActionEvent e) {
List components = selectionModel.getSelectedComponents();
- if (components.size() == 0) {
+ if (components.isEmpty()) {
return;
}
diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
index 7f5aed9e04..36f87e2daa 100644
--- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java
@@ -5,6 +5,7 @@
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
+import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
@@ -17,10 +18,11 @@
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
-import java.util.Arrays;
+import java.io.Serial;
import java.util.Comparator;
import javax.swing.AbstractAction;
+import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
@@ -81,6 +83,8 @@
import net.sf.openrocket.file.SimulationTableCSVExport;
import net.sf.openrocket.utils.TableRowTraversalPolicy;
+import static net.sf.openrocket.gui.main.BasicFrame.SHORTCUT_KEY;
+
@SuppressWarnings("serial")
public class SimulationPanel extends JPanel {
private static final Logger log = LoggerFactory.getLogger(SimulationPanel.class);
@@ -110,6 +114,9 @@ public class SimulationPanel extends JPanel {
private final JPopupMenu pm;
private final SimulationAction editSimulationAction;
+ private final SimulationAction cutSimulationAction;
+ private final SimulationAction copySimulationAction;
+ private final SimulationAction pasteSimulationAction;
private final SimulationAction runSimulationAction;
private final SimulationAction plotSimulationAction;
private final SimulationAction duplicateSimulationAction;
@@ -129,6 +136,9 @@ public SimulationPanel(OpenRocketDocument doc) {
// Simulation actions
SimulationAction newSimulationAction = new NewSimulationAction();
editSimulationAction = new EditSimulationAction();
+ cutSimulationAction = new CutSimulationAction();
+ copySimulationAction = new CopySimulationAction();
+ pasteSimulationAction = new PasteSimulationAction();
runSimulationAction = new RunSimulationAction();
plotSimulationAction = new PlotSimulationAction();
duplicateSimulationAction = new DuplicateSimulationAction();
@@ -155,7 +165,7 @@ public SimulationPanel(OpenRocketDocument doc) {
RocketActions.tieActionToButton(runButton, runSimulationAction, trans.get("simpanel.but.runsimulations"));
runButton.setToolTipText(trans.get("simpanel.but.ttip.runsimu"));
this.add(runButton, "gapright para");
-
+
//// Delete simulations button
deleteButton = new IconButton();
RocketActions.tieActionToButton(deleteButton, deleteSimulationAction, trans.get("simpanel.but.deletesimulations"));
@@ -176,6 +186,7 @@ public SimulationPanel(OpenRocketDocument doc) {
simulationTableModel = new SimulationTableModel();
simulationTable = new ColumnTable(simulationTableModel) {
+ @Serial
private static final long serialVersionUID = -5799340181229735630L;
};
ColumnTableRowSorter simulationTableSorter = new ColumnTableRowSorter(simulationTableModel);
@@ -185,9 +196,21 @@ public SimulationPanel(OpenRocketDocument doc) {
simulationTableModel.setColumnWidths(simulationTable.getColumnModel());
simulationTable.setFillsViewportHeight(true);
+ // Unregister the default actions that would otherwise conflict with RocketActions and their acceleration keys
+ InputMap im = simulationTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_E, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_D, SHORTCUT_KEY), "none");
+ im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, SHORTCUT_KEY), "none");
+
// Context menu
pm = new JPopupMenu();
pm.add(editSimulationAction);
+ pm.add(cutSimulationAction);
+ pm.add(copySimulationAction);
+ pm.add(pasteSimulationAction);
pm.add(duplicateSimulationAction);
pm.add(deleteSimulationAction);
pm.addSeparator();
@@ -248,7 +271,7 @@ else if (e.getButton() == MouseEvent.BUTTON3 && e.getClickCount() == 1) {
public void valueChanged(ListSelectionEvent event) {
if ((simulationTable.getSelectedRow() != previousSelectedRow) ||
(simulationTable.getSelectedRowCount() != previousSelectedRowCount)) {
- updateButtonStates();
+ updateActions();
previousSelectedRow = simulationTable.getSelectedRow();
previousSelectedRowCount = simulationTable.getSelectedRowCount();
}
@@ -279,7 +302,55 @@ public void componentChanged(ComponentChangeEvent e) {
JScrollPane scrollpane = new JScrollPane(simulationTable);
this.add(scrollpane, "spanx, grow, wrap rel");
- updateButtonStates();
+ updateActions();
+ }
+
+ /**
+ * Returns the action used for editing selected simulations.
+ * @return the action used for editing selected simulations.
+ */
+ public SimulationAction getEditSimulationAction() {
+ return editSimulationAction;
+ }
+
+ /**
+ * Returns the action used for cutting selected simulations.
+ * @return the action used for cutting selected simulations.
+ */
+ public SimulationAction getCutSimulationAction() {
+ return cutSimulationAction;
+ }
+
+ /**
+ * Returns the action used for copying selected simulations.
+ * @return the action used for copying selected simulations.
+ */
+ public SimulationAction getCopySimulationAction() {
+ return copySimulationAction;
+ }
+
+ /**
+ * Returns the action used for pasting simulations.
+ * @return the action used for pasting simulations.
+ */
+ public SimulationAction getPasteSimulationAction() {
+ return pasteSimulationAction;
+ }
+
+ /**
+ * Returns the action used for duplicating selected simulations.
+ * @return the action used for duplicating selected simulations.
+ */
+ public SimulationAction getDuplicateSimulationAction() {
+ return duplicateSimulationAction;
+ }
+
+ /**
+ * Returns the action used for deleting selected simulations.
+ * @return the action used for deleting selected simulations.
+ */
+ public SimulationAction getDeleteSimulationAction() {
+ return deleteSimulationAction;
}
public void updatePreviousSelection() {
@@ -323,55 +394,62 @@ private void plotSimulation() {
openDialog(true, sim);
}
- private void deleteSimulation() {
- int[] selection = simulationTable.getSelectedRows();
- if (selection.length == 0) {
+ private void deleteSimulations(Simulation[] sims) {
+ if (sims == null || sims.length == 0) {
return;
}
// Verify deletion
- boolean verify = Application.getPreferences().getBoolean(Preferences.CONFIRM_DELETE_SIMULATION, true);
- if (verify) {
-
- JPanel panel = new JPanel(new MigLayout());
- //// Do not ask me again
- JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
- panel.add(dontAsk, "wrap");
- //// You can change the default operation in the preferences.
- panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
-
- int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
- new Object[] {
- //// Delete the selected simulations?
- trans.get("simpanel.dlg.lbl.DeleteSim1"),
- //// This operation cannot be undone.
- trans.get("simpanel.dlg.lbl.DeleteSim2"),
- "",
- panel },
- //// Delete simulations
- trans.get("simpanel.dlg.lbl.DeleteSim3"),
- JOptionPane.OK_CANCEL_OPTION,
- JOptionPane.WARNING_MESSAGE);
- if (ret != JOptionPane.OK_OPTION)
- return;
-
- if (dontAsk.isSelected()) {
- Application.getPreferences().putBoolean(Preferences.CONFIRM_DELETE_SIMULATION, false);
- }
+ if (!verifyDeleteSimulation()) {
+ return;
}
// Delete simulations
- for (int i = 0; i < selection.length; i++) {
- selection[i] = simulationTable.convertRowIndexToModel(selection[i]);
- }
- Arrays.sort(selection);
- for (int i = selection.length - 1; i >= 0; i--) {
- document.removeSimulation(selection[i]);
+ for (Simulation sim : sims) {
+ document.removeSimulation(sim);
}
+
simulationTableModel.fireTableDataChanged();
updatePreviousSelection();
takeTheSpotlight();
}
+ private void deleteSimulations() {
+ deleteSimulations(getSelectedSimulations());
+ }
+
+ private boolean verifyDeleteSimulation() {
+ boolean verify = Application.getPreferences().getConfirmSimDeletion();
+ if (!verify) {
+ return true;
+ }
+
+ JPanel panel = new JPanel(new MigLayout());
+ //// Do not ask me again
+ JCheckBox dontAsk = new JCheckBox(trans.get("simpanel.checkbox.donotask"));
+ panel.add(dontAsk, "wrap");
+ //// You can change the default operation in the preferences.
+ panel.add(new StyledLabel(trans.get("simpanel.lbl.defpref"), -2));
+
+ int ret = JOptionPane.showConfirmDialog(SimulationPanel.this,
+ new Object[] {
+ //// Delete the selected simulations?
+ trans.get("simpanel.dlg.lbl.DeleteSim1"),
+ //// This operation cannot be undone.
+ trans.get("simpanel.dlg.lbl.DeleteSim2"),
+ "",
+ panel },
+ //// Delete simulations
+ trans.get("simpanel.dlg.lbl.DeleteSim3"),
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.WARNING_MESSAGE);
+
+ if (dontAsk.isSelected()) {
+ Application.getPreferences().setConfirmSimDeletion(false);
+ }
+
+ return ret == JOptionPane.OK_OPTION;
+ }
+
private void runSimulation() {
Simulation[] sims = getSelectedSimulations();
if (sims == null) return;
@@ -479,56 +557,113 @@ private Simulation[] getSelectedSimulations() {
return sims;
}
+ /**
+ * Full simulation copying
+ */
+ public void copySimulationsAction() {
+ Simulation[] sims = getSelectedSimulations();
+
+ if (sims == null || sims.length == 0)
+ return;
- private void copySimulationAction() {
- int numCols=simulationTable.getColumnCount();
- int numRows=simulationTable.getSelectedRowCount();
- int[] rowsSelected=simulationTable.getSelectedRows();
+ Simulation[] simsCopy = new Simulation[sims.length];
+ for (int i=0; i < sims.length; i++) {
+ simsCopy[i] = sims[i].copy();
+ }
- if (numRows!=rowsSelected[rowsSelected.length-1]-rowsSelected[0]+1 || numRows!=rowsSelected.length) {
+ OpenRocketClipboard.setClipboard(simsCopy);
+ copySimulationValues();
+ }
- JOptionPane.showMessageDialog(null, "Invalid Copy Selection", "Invalid Copy Selection", JOptionPane.ERROR_MESSAGE);
+ /**
+ * Only copy the simulation table values to the clipboard. (not actual Simulation copying)
+ */
+ public void copySimulationValues() {
+ int numCols = simulationTable.getColumnCount();
+ int numRows = simulationTable.getSelectedRowCount();
+ int[] rowsSelected = simulationTable.getSelectedRows();
+
+ if (numRows != rowsSelected.length) {
+ JOptionPane.showMessageDialog(this, trans.get("simpanel.msg.invalidCopySelection"),
+ trans.get("simpanel.msg.invalidCopySelection"), JOptionPane.ERROR_MESSAGE);
return;
}
- StringBuilder excelStr =new StringBuilder();
- for (int k = 1; k < numCols; k++) {
- excelStr.append(simulationTable.getColumnName(k));
- if (k < numCols-1) {
- excelStr.append("\t");
+ StringBuilder valuesStr = new StringBuilder();
+
+ // Copy the column names
+ valuesStr.append(trans.get("simpanel.col.Status")).append("\t");
+ for (int i = 1; i < numCols; i++) {
+ valuesStr.append(simulationTable.getColumnName(i));
+ if (i < numCols-1) {
+ valuesStr.append("\t");
}
}
- excelStr.append("\n");
+ valuesStr.append("\n");
+
+ // Copy the values
for (int i = 0; i < numRows; i++) {
- for (int j = 1; j < numCols; j++) {
- excelStr.append(simulationTable.getValueAt(rowsSelected[i], j));
+ for (int j = 0; j < numCols; j++) {
+ Object value = simulationTable.getValueAt(rowsSelected[i], j);
+ valuesStr.append(value == null ? "" : value.toString());
if (j < numCols-1) {
- excelStr.append("\t");
+ valuesStr.append("\t");
}
}
- excelStr.append("\n");
+ valuesStr.append("\n");
}
- StringSelection sel = new StringSelection(excelStr.toString());
+ StringSelection sel = new StringSelection(valuesStr.toString());
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
cb.setContents(sel, sel);
}
- private void duplicateSimulation() {
- Simulation[] sims = getSelectedSimulations();
- if (sims == null) return;
+ private void pasteSimulationsAction() {
+ Simulation[] sims = OpenRocketClipboard.getClipboardSimulations();
+ if (sims == null || sims.length == 0) {
+ return;
+ }
+
+ duplicateSimulation(sims, simulationTable.getSelectedRow() + 1);
+ }
+
+ /**
+ * Duplicates the provided simulations at a certain index and selects them in the simulation table.
+ * @param sims The simulations to duplicate
+ * @param index The index to insert the simulations at (e.g. '1' to insert after the first simulation)
+ */
+ public void duplicateSimulation(Simulation[] sims, int index) {
+ if (sims == null || sims.length == 0) return;
+ // TODO: the undoing doesn't do anything...
+ if (sims.length == 1) {
+ document.addUndoPosition("Duplicate " + sims[0].getName());
+ } else {
+ document.addUndoPosition("Duplicate simulations");
+ }
+
+ index = index >= 0 ? index : document.getSimulationCount();
+ int newIndex = index;
for (Simulation s: sims) {
Simulation copy = s.duplicateSimulation(document.getRocket());
String name = copy.getName();
if (name.matches(OpenRocketDocument.SIMULATION_NAME_PREFIX + "[0-9]+ *")) {
copy.setName(document.getNextSimulationName());
}
- document.addSimulation(copy);
+ document.addSimulation(copy, newIndex);
+ newIndex++; // Ensure simulations are in the correct order
}
- simulationTable.getSelectionModel().setSelectionInterval(simulationTable.getRowCount()-sims.length,simulationTable.getRowCount()-1);
+ simulationTableModel.fireTableDataChanged();
+ simulationTable.getSelectionModel().setSelectionInterval(index, newIndex-1);
+ }
+ /**
+ * Duplicates the provided simulations to the end and selects them in the simulation table.
+ * @param sims The simulations to duplicate.
+ */
+ public void duplicateSimulation(Simulation[] sims) {
+ duplicateSimulation(sims, document.getSimulationCount());
}
/**
@@ -542,13 +677,24 @@ public AbstractAction getExportSimulationTableAsCSVAction() {
protected void doPopup(MouseEvent e) {
pm.show(e.getComponent(), e.getX(), e.getY());
}
+
+ /**
+ * Add a listener to the simulation table selection model.
+ * @param listener the listener to add.
+ */
+ public void addSimulationTableListSelectionListener(ListSelectionListener listener) {
+ simulationTable.getSelectionModel().addListSelectionListener(listener);
+ }
- private void updateButtonStates() {
+ public void updateActions() {
editSimulationAction.updateEnabledState();
+ cutSimulationAction.updateEnabledState();
+ copySimulationAction.updateEnabledState();
+ pasteSimulationAction.updateEnabledState();
+ duplicateSimulationAction.updateEnabledState();
deleteSimulationAction.updateEnabledState();
runSimulationAction.updateEnabledState();
plotSimulationAction.updateEnabledState();
- duplicateSimulationAction.updateEnabledState();
simTableExportAction.updateEnabledState();
selectedSimsExportAction.updateEnabledState();
}
@@ -595,6 +741,61 @@ public ListSelectionModel getSimulationListSelectionModel() {
return simulationTable.getSelectionModel();
}
+ private String getSimulationToolTip(Simulation sim, boolean includeSimName) {
+ String tip;
+ FlightData data = sim.getSimulatedData();
+
+ tip = "";
+ if (includeSimName) {
+ tip += "" + sim.getName() + "
";
+ }
+ switch (sim.getStatus()) {
+ case CANT_RUN:
+ tip += trans.get("simpanel.ttip.noData")+"
";
+ break;
+ case LOADED:
+ tip += trans.get("simpanel.ttip.loaded") + "
";
+ break;
+ case UPTODATE:
+ tip += trans.get("simpanel.ttip.uptodate") + "
";
+ break;
+
+ case OUTDATED:
+ tip += trans.get("simpanel.ttip.outdated") + "
";
+ break;
+
+ case EXTERNAL:
+ tip += trans.get("simpanel.ttip.external") + "
";
+ return tip;
+
+ case NOT_SIMULATED:
+ tip += trans.get("simpanel.ttip.notSimulated");
+ return tip;
+ }
+
+ if (data == null) {
+ tip += trans.get("simpanel.ttip.noData");
+ return tip;
+ }
+ WarningSet warnings = data.getWarningSet();
+
+ if (warnings.isEmpty()) {
+ tip += trans.get("simpanel.ttip.noWarnings");
+ return tip;
+ }
+
+ tip += trans.get("simpanel.ttip.warnings");
+ for (Warning w : warnings) {
+ tip += "
" + w.toString();
+ }
+
+ return tip;
+ }
+
+ private String getSimulationToolTip(Simulation sim) {
+ return getSimulationToolTip(sim, true);
+ }
+
private void openDialog(boolean plotMode, boolean isNewSimulation, final Simulation... sims) {
SimulationEditDialog d = new SimulationEditDialog(SwingUtilities.getWindowAncestor(this), document, isNewSimulation, sims);
if (plotMode) {
@@ -627,7 +828,7 @@ private void fireMaintainSelection() {
}
}
- private abstract static class SimulationAction extends AbstractAction {
+ public abstract static class SimulationAction extends AbstractAction {
private static final long serialVersionUID = 1L;
public abstract void updateEnabledState();
@@ -653,7 +854,8 @@ public void updateEnabledState() {
class EditSimulationAction extends SimulationAction {
public EditSimulationAction() {
- putValue(NAME, trans.get("simpanel.pop.edit"));
+ this.putValue(NAME, trans.get("simpanel.pop.edit"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.edit.ttip"));
this.putValue(MNEMONIC_KEY, KeyEvent.VK_E);
this.putValue(ACCELERATOR_KEY, RocketActions.EDIT_KEY_STROKE);
this.putValue(SMALL_ICON, Icons.EDIT_EDIT);
@@ -664,16 +866,125 @@ public void actionPerformed(ActionEvent e) {
editSimulation();
}
+ @Override
+ public void updateEnabledState() {
+ this.setEnabled(simulationTable.getSelectedRowCount() > 0);
+ }
+ }
+
+ class CutSimulationAction extends SimulationAction {
+ public CutSimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.cut"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.cut.ttip"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_X);
+ this.putValue(ACCELERATOR_KEY, RocketActions.CUT_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_CUT);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Simulation[] sims = getSelectedSimulations();
+
+ if (sims == null || sims.length == 0)
+ return;
+
+ copySimulationsAction();
+ deleteSimulations(sims);
+ }
+
@Override
public void updateEnabledState() {
setEnabled(simulationTable.getSelectedRowCount() > 0);
}
}
+ class CopySimulationAction extends SimulationAction {
+ public CopySimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.copy"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.copy.ttip"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_C);
+ this.putValue(ACCELERATOR_KEY, RocketActions.COPY_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_COPY);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ copySimulationsAction();
+ }
+
+ @Override
+ public void updateEnabledState() {
+ this.setEnabled(simulationTable.getSelectedRowCount() > 0);
+ }
+ }
+
+ class PasteSimulationAction extends SimulationAction {
+ public PasteSimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.paste"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.paste.ttip"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_V);
+ this.putValue(ACCELERATOR_KEY, RocketActions.PASTE_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_PASTE);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ pasteSimulationsAction();
+ }
+
+ @Override
+ public void updateEnabledState() {
+ this.setEnabled((simulationTable.getSelectedRowCount() > 0) &&
+ (OpenRocketClipboard.getClipboardSimulations() != null) && (OpenRocketClipboard.getClipboardSimulations().length > 0));
+ }
+ }
+
+ class DuplicateSimulationAction extends SimulationAction {
+ public DuplicateSimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.duplicate"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.duplicate.ttip"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_D);
+ this.putValue(ACCELERATOR_KEY, RocketActions.DUPLICATE_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_DUPLICATE);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ duplicateSimulation(getSelectedSimulations());
+ }
+
+ @Override
+ public void updateEnabledState() {
+ this.setEnabled(simulationTable.getSelectedRowCount() > 0);
+ }
+ }
+
+ class DeleteSimulationAction extends SimulationAction {
+ public DeleteSimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.delete"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.delete.ttip"));
+ this.putValue(MNEMONIC_KEY, KeyEvent.VK_DELETE);
+ this.putValue(ACCELERATOR_KEY, RocketActions.DELETE_KEY_STROKE);
+ this.putValue(SMALL_ICON, Icons.EDIT_DELETE);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ deleteSimulations();
+ }
+
+ @Override
+ public void updateEnabledState() {
+ this.setEnabled(simulationTable.getSelectedRowCount() > 0);
+ }
+ }
+
+
class RunSimulationAction extends SimulationAction {
public RunSimulationAction() {
- putValue(NAME, trans.get("simpanel.pop.run"));
- putValue(SMALL_ICON, Icons.SIM_RUN);
+ this.putValue(NAME, trans.get("simpanel.pop.run"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.run.ttip"));
+ this.putValue(SMALL_ICON, Icons.SIM_RUN);
}
@Override
@@ -683,26 +994,25 @@ public void actionPerformed(ActionEvent e) {
@Override
public void updateEnabledState() {
- setEnabled(simulationTable.getSelectedRowCount() > 0);
+ this.setEnabled(simulationTable.getSelectedRowCount() > 0);
}
}
- class DeleteSimulationAction extends SimulationAction {
- public DeleteSimulationAction() {
- putValue(NAME, trans.get("simpanel.pop.delete"));
- putValue(MNEMONIC_KEY, KeyEvent.VK_D);
- putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
- putValue(SMALL_ICON, Icons.EDIT_DELETE);
+ class PlotSimulationAction extends SimulationAction {
+ public PlotSimulationAction() {
+ this.putValue(NAME, trans.get("simpanel.pop.plot"));
+ this.putValue(SHORT_DESCRIPTION, trans.get("simpanel.pop.plot.ttip"));
+ this.putValue(SMALL_ICON, Icons.SIM_PLOT);
}
@Override
public void actionPerformed(ActionEvent e) {
- deleteSimulation();
+ plotSimulation();
}
@Override
public void updateEnabledState() {
- setEnabled(simulationTable.getSelectedRowCount() > 0);
+ this.setEnabled(simulationTable.getSelectedRowCount() == 1);
}
}
@@ -712,8 +1022,8 @@ public void updateEnabledState() {
class ExportSimulationTableAsCSVAction extends SimulationAction {
public ExportSimulationTableAsCSVAction() {
- putValue(NAME, trans.get("simpanel.pop.exportSimTableToCSV"));
- putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
+ this.putValue(NAME, trans.get("simpanel.pop.exportSimTableToCSV"));
+ this.putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
}
@Override
@@ -723,7 +1033,7 @@ public void actionPerformed(ActionEvent arg0) {
@Override
public void updateEnabledState() {
- setEnabled(simulationTableModel != null && simulationTableModel.getRowCount() > 0);
+ this.setEnabled(simulationTableModel != null && simulationTableModel.getRowCount() > 0);
}
}
@@ -734,8 +1044,8 @@ public void updateEnabledState() {
class ExportSelectedSimulationsAsCSVAction extends SimulationAction {
public ExportSelectedSimulationsAsCSVAction() {
- putValue(NAME, trans.get("simpanel.pop.exportSelectedSimsToCSV"));
- putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
+ this.putValue(NAME, trans.get("simpanel.pop.exportSelectedSimsToCSV"));
+ this.putValue(SMALL_ICON, Icons.SIM_TABLE_EXPORT);
}
@Override
@@ -745,47 +1055,13 @@ public void actionPerformed(ActionEvent arg0) {
@Override
public void updateEnabledState() {
- setEnabled(simulationTableModel != null && simulationTableModel.getRowCount() > 0 &&
+ this.setEnabled(simulationTableModel != null && simulationTableModel.getRowCount() > 0 &&
simulationTable.getSelectedRowCount() > 0);
}
}
- class PlotSimulationAction extends SimulationAction {
- public PlotSimulationAction() {
- putValue(NAME, trans.get("simpanel.pop.plot"));
- putValue(SMALL_ICON, Icons.SIM_PLOT);
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- plotSimulation();
- }
-
- @Override
- public void updateEnabledState() {
- setEnabled(simulationTable.getSelectedRowCount() == 1);
- }
- }
- class DuplicateSimulationAction extends SimulationAction {
- public DuplicateSimulationAction() {
- putValue(NAME, trans.get("simpanel.pop.duplicate"));
- putValue(MNEMONIC_KEY, KeyEvent.VK_D);
- putValue(ACCELERATOR_KEY, RocketActions.DUPLICATE_KEY_STROKE);
- putValue(SMALL_ICON, Icons.EDIT_DUPLICATE);
- }
-
- @Override
- public void actionPerformed(ActionEvent e) {
- duplicateSimulation();
- }
-
- @Override
- public void updateEnabledState() {
- setEnabled(simulationTable.getSelectedRowCount() > 0);
- }
- }
public static class CellTransferable implements Transferable {
@@ -852,53 +1128,24 @@ public Component getTableCellRendererComponent(JTable table,
}
return component;
}
+ }
- private String getSimulationToolTip(Simulation sim) {
- String tip;
- FlightData data = sim.getSimulatedData();
-
- tip = "" + sim.getName() + "
";
- switch (sim.getStatus()) {
- case CANT_RUN:
- tip += trans.get("simpanel.ttip.noData")+"
";
- break;
- case LOADED:
- tip += trans.get("simpanel.ttip.loaded") + "
";
- break;
- case UPTODATE:
- tip += trans.get("simpanel.ttip.uptodate") + "
";
- break;
-
- case OUTDATED:
- tip += trans.get("simpanel.ttip.outdated") + "
";
- break;
-
- case EXTERNAL:
- tip += trans.get("simpanel.ttip.external") + "
";
- return tip;
-
- case NOT_SIMULATED:
- tip += trans.get("simpanel.ttip.notSimulated");
- return tip;
- }
-
- if (data == null) {
- tip += trans.get("simpanel.ttip.noData");
- return tip;
- }
- WarningSet warnings = data.getWarningSet();
+ private class StatusLabel extends StyledLabel {
+ private Simulation simulation;
- if (warnings.isEmpty()) {
- tip += trans.get("simpanel.ttip.noWarnings");
- return tip;
- }
+ public StatusLabel(Simulation simulation, float size) {
+ super(size);
+ this.simulation = simulation;
+ }
- tip += trans.get("simpanel.ttip.warnings");
- for (Warning w : warnings) {
- tip += "
" + w.toString();
- }
+ public void replaceSimulation(Simulation simulation) {
+ this.simulation = simulation;
+ }
- return tip;
+ @Override
+ public String toString() {
+ String text = getSimulationToolTip(simulation, false);
+ return text.replace("
", "-").replaceAll("<[^>]*>","");
}
}
@@ -909,31 +1156,33 @@ public SimulationTableModel() {
super(
//// Status and warning column
new Column("") {
- private JLabel label = null;
+ private StatusLabel label = null;
@Override
public Object getValueAt(int row) {
if (row < 0 || row >= document.getSimulationCount())
return null;
+ Simulation simulation = document.getSimulation(row);
+
// Initialize the label
if (label == null) {
- label = new StyledLabel(2f);
+ label = new StatusLabel(simulation, 2f);
label.setIconTextGap(1);
// label.setFont(label.getFont().deriveFont(Font.BOLD));
+ } else {
+ label.replaceSimulation(simulation);
}
// Set simulation status icon
- Simulation.Status status = document.getSimulation(row).getStatus();
+ Simulation.Status status = simulation.getStatus();
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));
// Set warning marker
if (status == Simulation.Status.NOT_SIMULATED ||
status == Simulation.Status.EXTERNAL) {
-
label.setText("");
-
} else {
WarningSet w = document.getSimulation(row).getSimulatedWarnings();
@@ -1160,7 +1409,7 @@ public void takeTheSpotlight() {
return;
}
if (previousSelection == null || previousSelection.length == 0) {
- simulationTable.setRowSelectionInterval(0, 0);
+ simulationTable.getSelectionModel().setSelectionInterval(0, 0);
} else {
simulationTable.clearSelection();
for (int row : previousSelection) {
@@ -1170,5 +1419,6 @@ public void takeTheSpotlight() {
simulationTable.addRowSelectionInterval(row, row);
}
}
+ updateActions();
}
}
diff --git a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
index a852e94b7c..e5d422e36f 100644
--- a/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/main/componenttree/ComponentTreeRenderer.java
@@ -1,20 +1,22 @@
package net.sf.openrocket.gui.main.componenttree;
+import java.awt.BorderLayout;
import java.awt.Component;
-import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.util.List;
+import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.UIManager;
+import javax.swing.border.Border;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreePath;
import net.sf.openrocket.gui.main.ComponentIcons;
-import net.sf.openrocket.gui.util.Icons;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.rocketcomponent.MassComponent;
import net.sf.openrocket.rocketcomponent.MassComponent.MassComponentType;
@@ -34,45 +36,83 @@ public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus1) {
- Component comp = super.getTreeCellRendererComponent(tree, value, sel,
- expanded, leaf, row, hasFocus1);
+ // Create a new JPanel
+ JPanel panel = new JPanel();
+ panel.setOpaque(false); // Set this to false if you want to keep the tree's default background intact
+ panel.setLayout(new BorderLayout());
+
+ // Create two JLabels, one for the icon and one for the text
+ JLabel iconLabel = new JLabel();
+ JLabel textLabel = new JLabel();
+
+ // Retrieve the component from the super method
+ Component comp = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus1);
if (tree == null) return comp;
TreePath[] paths = tree.getSelectionPaths();
List components = null;
if (paths != null && paths.length > 0) {
components = new ArrayList<>(ComponentTreeModel.componentsFromPaths(paths));
}
+ if (comp instanceof JLabel) {
+ textLabel.setText(((JLabel) comp).getText());
- // Set icon
+ // Set the font to the tree font
+ Font treeFont = UIManager.getFont("Tree.font");
+ textLabel.setFont(treeFont);
+ }
+
+ // Set the icon
RocketComponent c = (RocketComponent) value;
+ Border iconMarginBorder = BorderFactory.createEmptyBorder(0, 0, 0, 4); // 4-pixel gap to the right of the icon
if (c.getClass().isAssignableFrom(MassComponent.class)) {
MassComponentType t = ((MassComponent) c).getMassComponentType();
- setIcon(ComponentIcons.getSmallMassTypeIcon(t));
+ iconLabel.setIcon(ComponentIcons.getSmallMassTypeIcon(t));
+ iconLabel.setBorder(iconMarginBorder);
+ } else {
+ iconLabel.setIcon(ComponentIcons.getSmallIcon(value.getClass()));
+ iconLabel.setBorder(iconMarginBorder);
+ }
+
+ // Add the JLabels to the JPanel
+ panel.add(iconLabel, BorderLayout.WEST);
+ panel.add(textLabel, BorderLayout.CENTER);
+
+ // Set the background and foreground colors of the text JLabel
+ if (sel) {
+ textLabel.setOpaque(true);
+ textLabel.setBackground(GUIUtil.getUITheme().getTextSelectionBackgroundColor());
+ textLabel.setForeground(GUIUtil.getUITheme().getTextSelectionForegroundColor());
} else {
- setIcon(ComponentIcons.getSmallIcon(value.getClass()));
+ textLabel.setOpaque(true); // Set this to true to allow the background color to be visible
+ textLabel.setBackground(GUIUtil.getUITheme().getComponentTreeBackgroundColor());
+ textLabel.setForeground(GUIUtil.getUITheme().getComponentTreeForegroundColor());
}
+
+ comp = panel;
+
+ // Add mass/CG/CD overridden icons
if (c.isMassOverridden() || c.getMassOverriddenBy() != null ||
c.isCGOverridden() || c.getCGOverriddenBy() != null ||
c.isCDOverridden() || c.getCDOverriddenBy() != null) {
JPanel p = new JPanel();
p.setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1));
- p.setBackground(UIManager.getColor("Tree.textBackground"));
- p.setForeground(UIManager.getColor("Tree.textForeground"));
+ p.setBackground(GUIUtil.getUITheme().getComponentTreeBackgroundColor());
+ p.setForeground(GUIUtil.getUITheme().getComponentTreeForegroundColor());
p.add(comp/* , BorderLayout.WEST */);
if (c.getMassOverriddenBy() != null) {
- p.add(new JLabel(Icons.MASS_OVERRIDE_SUBCOMPONENT));
+ p.add(new JLabel(GUIUtil.getUITheme().getMassOverrideSubcomponentIcon()));
} else if (c.isMassOverridden()) {
- p.add(new JLabel(Icons.MASS_OVERRIDE));
+ p.add(new JLabel(GUIUtil.getUITheme().getMassOverrideIcon()));
}
if (c.getCGOverriddenBy() != null) {
- p.add(new JLabel(Icons.CG_OVERRIDE_SUBCOMPONENT));
+ p.add(new JLabel(GUIUtil.getUITheme().getCGOverrideSubcomponentIcon()));
} else if (c.isCGOverridden()) {
- p.add(new JLabel(Icons.CG_OVERRIDE));
+ p.add(new JLabel(GUIUtil.getUITheme().getCGOverrideIcon()));
}
if (c.getCDOverriddenBy() != null) {
- p.add(new JLabel(Icons.CD_OVERRIDE_SUBCOMPONENT));
+ p.add(new JLabel(GUIUtil.getUITheme().getCDOverrideSubcomponentIcon()));
} else if (c.isCDOverridden()) {
- p.add(new JLabel(Icons.CD_OVERRIDE));
+ p.add(new JLabel(GUIUtil.getUITheme().getCDOverrideIcon()));
}
// Make sure the tooltip also works on the override icons
diff --git a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
index 5baec6802c..ee9e53dec6 100644
--- a/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
+++ b/swing/src/net/sf/openrocket/gui/main/flightconfigpanel/FlightConfigurablePanel.java
@@ -23,6 +23,7 @@
import javax.swing.table.DefaultTableCellRenderer;
import net.sf.openrocket.gui.main.FlightConfigurationPanel;
+import net.sf.openrocket.gui.util.SwingPreferences;
import net.sf.openrocket.util.ArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -44,6 +45,8 @@
public abstract class FlightConfigurablePanel extends JPanel implements ComponentChangeListener {
protected static final Translator trans = Application.getTranslator();
+ protected static final SwingPreferences prefs = (SwingPreferences) Application.getPreferences();
+
private static final Logger log = LoggerFactory.getLogger(FlightConfigurablePanel.class);
protected RocketDescriptor descriptor = Application.getInjector().getInstance(RocketDescriptor.class);
@@ -387,12 +390,12 @@ private void setSelected(JComponent c, JTable table, boolean isSelected, boolean
protected final void shaded(JLabel label) {
GUIUtil.changeFontStyle(label, Font.ITALIC);
- label.setForeground(Color.GRAY);
+ label.setForeground(GUIUtil.getUITheme().getDimTextColor());
}
protected final void regular(JLabel label) {
GUIUtil.changeFontStyle(label, Font.PLAIN);
- label.setForeground(Color.BLACK);
+ label.setForeground(GUIUtil.getUITheme().getTextColor());
}
protected abstract JLabel format( T component, FlightConfigurationId configId, JLabel label );
diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
index db19ab4967..c2f6db4dba 100644
--- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
+++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlotDialog.java
@@ -3,8 +3,6 @@
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
@@ -31,7 +29,6 @@
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.startup.Preferences;
-import net.sf.openrocket.util.Color;
import net.sf.openrocket.gui.widgets.SelectColorButton;
import org.jfree.chart.ChartPanel;
@@ -44,9 +41,8 @@
* @author Sampo Niskanen
*/
public class SimulationPlotDialog extends JDialog {
-
private static final Translator trans = Application.getTranslator();
-
+
private SimulationPlotDialog(Window parent, Simulation simulation, PlotConfiguration config) {
//// Flight data plot
super(parent, simulation.getName());
@@ -77,7 +73,7 @@ private SimulationPlotDialog(Window parent, Simulation simulation, PlotConfigura
// Add warning if X axis type is not time
if (config.getDomainAxisType() != FlightDataType.TYPE_TIME) {
JLabel msg = new StyledLabel(trans.get("PlotDialog.lbl.timeSeriesWarning"), -2);
- msg.setForeground(Color.DARK_RED.toAWTColor());
+ msg.setForeground(GUIUtil.getUITheme().getDarkWarningColor());
panel.add(msg, "wrap");
}
diff --git a/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java b/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
index d3a4068f0b..edc683f1cc 100644
--- a/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
+++ b/swing/src/net/sf/openrocket/gui/rocketfigure/ComponentAssemblyShapes.java
@@ -40,11 +40,6 @@ public static RocketComponentShape[] getShapesSide(final RocketComponent compone
Shape[] s = EmptyShapes.getShapesSideWithSelectionSquare(correctedTransform, markerRadius);
RocketComponentShape[] shapes = RocketComponentShape.toArray(s, component);
- // Set the color of the shapes
- Color color = getColor(component);
- for (int i = 0; i < shapes.length - 1; i++) {
- shapes[i].setColor(color);
- }
shapes[shapes.length - 1].setColor(Color.INVISIBLE);
return shapes;
@@ -69,11 +64,7 @@ public static RocketComponentShape[] getShapesBack(final RocketComponent compone
Shape[] s = EmptyShapes.getShapesBackWithSelectionSquare(correctedTransform, markerRadius);
RocketComponentShape[] shapes = RocketComponentShape.toArray(s, component);
- // Set the color of the shapes
- Color color = getColor(component);
- for (int i = 0; i < shapes.length - 1; i++) {
- shapes[i].setColor(color);
- }
+ shapes[shapes.length - 1].setColor(Color.INVISIBLE);
return shapes;
}
@@ -86,14 +77,4 @@ public static RocketComponentShape[] getShapesBack(final RocketComponent compone
private static double getDisplayRadius(RocketComponent component) {
return component.getRocket().getBoundingRadius() * 0.03;
}
-
- private static Color getColor(RocketComponent component) {
- if (component instanceof PodSet) {
- return new Color(160,160,215);
- } else if (component instanceof ParallelStage) {
- return new Color(198,163,184);
- } else {
- return new Color(160, 160, 160);
- }
- }
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
index 25585320c4..01f5120b60 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/AbstractScaleFigure.java
@@ -70,7 +70,7 @@ public AbstractScaleFigure() {
this.setPreferredSize(new Dimension(100,100));
setSize(100,100);
- setBackground(Color.WHITE);
+ setBackground(GUIUtil.getUITheme().getBackgroundColor());
setOpaque(true);
}
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
index 83d5f96009..cf56625745 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/FinPointFigure.java
@@ -1,7 +1,6 @@
package net.sf.openrocket.gui.scalefigure;
import java.awt.BasicStroke;
-import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
@@ -15,6 +14,7 @@
import java.util.LinkedList;
import java.util.List;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.rocketcomponent.FreeformFinSet;
import net.sf.openrocket.rocketcomponent.RocketComponent;
import net.sf.openrocket.rocketcomponent.SymmetricComponent;
@@ -31,11 +31,7 @@
@SuppressWarnings("serial")
public class FinPointFigure extends AbstractScaleFigure {
-
- //private final static Logger log = LoggerFactory.getLogger(FinPointFigure.class);
- private static final Color GRID_MAJOR_LINE_COLOR = new Color( 0, 0, 255, 80);
- private static final Color GRID_MINOR_LINE_COLOR = new Color( 0, 0, 255, 30);
private static final int GRID_LINE_BASE_WIDTH_PIXELS = 1;
private static final int LINE_WIDTH_FIN_PIXELS = 1;
@@ -45,8 +41,6 @@ public class FinPointFigure extends AbstractScaleFigure {
private static final int LINE_WIDTH_BOX_PIXELS = 1;
private static final float BOX_WIDTH_PIXELS = 12;
private static final float SELECTED_BOX_WIDTH_PIXELS = BOX_WIDTH_PIXELS + 4;
- private static final Color POINT_COLOR = new Color(200, 0, 0, 255);
- private static final Color SELECTED_POINT_COLOR = new Color(200, 0, 0, 255);
private static final double MINOR_TICKS = 10.0;
private static final double MAJOR_TICKS = 100.0;
@@ -65,9 +59,8 @@ public class FinPointFigure extends AbstractScaleFigure {
public FinPointFigure(FreeformFinSet finset) {
this.finset = finset;
-
- // useful for debugging -- shows a contrast against un-drawn space.
- setBackground(Color.WHITE);
+
+ setBackground(GUIUtil.getUITheme().getBackgroundColor());
setOpaque(true);
updateFigure();
@@ -129,11 +122,11 @@ public void paintBackgroundGrid( Graphics2D g2) {
Line2D.Double line = new Line2D.Double();
for (Tick t : verticalTicks) {
if (t.major) {
- g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointGridMajorLineColor());
line.setLine( t.value, y0, t.value, y1);
g2.draw(line);
}else{
- g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointGridMinorLineColor());
line.setLine( t.value, y0, t.value, y1);
g2.draw(line);
}
@@ -143,11 +136,11 @@ public void paintBackgroundGrid( Graphics2D g2) {
Tick[] horizontalTicks = unit.getTicks(y0, y1, MINOR_TICKS / this.scale, MAJOR_TICKS / this.scale);
for (Tick t : horizontalTicks) {
if (t.major) {
- g2.setColor(FinPointFigure.GRID_MAJOR_LINE_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointGridMajorLineColor());
line.setLine( x0, t.value, x1, t.value);
g2.draw(line);
}else{
- g2.setColor(FinPointFigure.GRID_MINOR_LINE_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointGridMinorLineColor());
line.setLine( x0, t.value, x1, t.value);
g2.draw(line);
}
@@ -170,7 +163,7 @@ private void paintBodyTransition( Graphics2D g2){
// setup lines
final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale );
g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getFinPointBodyLineColor());
Transition body = (Transition) finset.getParent();
final float xResolution_m = 0.01f; // distance between draw points, in meters
@@ -220,7 +213,7 @@ private void paintBodyTube( Graphics2D g2){
final float bodyLineWidth = (float) ( LINE_WIDTH_BODY_PIXELS / scale );
g2.setStroke(new BasicStroke( bodyLineWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getFinPointBodyLineColor());
g2.draw(shape);
}
@@ -237,7 +230,7 @@ private void paintFinShape(final Graphics2D g2){
final float finEdgeWidth_m = (float) (LINE_WIDTH_FIN_PIXELS / scale );
g2.setStroke(new BasicStroke( finEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getFinPointBodyLineColor());
g2.draw(shape);
}
@@ -251,7 +244,7 @@ private void paintFinHandles(final Graphics2D g2) {
final float boxEdgeWidth_m = (float) ( LINE_WIDTH_BOX_PIXELS / scale );
g2.setStroke(new BasicStroke( boxEdgeWidth_m, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
- g2.setColor(POINT_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointPointColor());
finPointHandles = new Rectangle2D.Double[ drawPoints.length];
for (int currentIndex = 0; currentIndex < drawPoints.length; currentIndex++) {
@@ -264,11 +257,11 @@ private void paintFinHandles(final Graphics2D g2) {
final Rectangle2D.Double selectedPointHighlight = new Rectangle2D.Double(c.x - selBoxHalfWidth, c.y - selBoxHalfWidth, selBoxWidth, selBoxWidth);
// switch to the highlight color
- g2.setColor(SELECTED_POINT_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointSelectedPointColor());
g2.draw(selectedPointHighlight);
// reset to the normal color
- g2.setColor(POINT_COLOR);
+ g2.setColor(GUIUtil.getUITheme().getFinPointPointColor());
}
// normal boxes
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
index df1ebb08d3..22f4d8d673 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketFigure.java
@@ -17,6 +17,7 @@
import java.util.*;
import java.util.Map.Entry;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.rocketcomponent.AxialStage;
import net.sf.openrocket.rocketcomponent.ParallelStage;
import net.sf.openrocket.rocketcomponent.PodSet;
@@ -257,7 +258,7 @@ else if (currentViewType == RocketPanel.VIEW_TYPE.BackView)
// Set component color and line style
net.sf.openrocket.util.Color color = rcs.color;
if (color == null) {
- color = Application.getPreferences().getDefaultColor(c.getClass());
+ color = ((SwingPreferences) Application.getPreferences()).getDefaultColor(c.getClass());
}
g2.setColor(ColorConversion.toAwtColor(color));
@@ -290,8 +291,8 @@ else if (currentViewType == RocketPanel.VIEW_TYPE.BackView)
RenderingHints.VALUE_STROKE_NORMALIZE);
// Draw motors
- Color fillColor = ((SwingPreferences)Application.getPreferences()).getMotorFillColor();
- Color borderColor = ((SwingPreferences)Application.getPreferences()).getMotorBorderColor();
+ Color fillColor = GUIUtil.getUITheme().getMotorFillColor();
+ Color borderColor = GUIUtil.getUITheme().getMotorBorderColor();
FlightConfiguration config = rocket.getSelectedConfiguration();
for (MotorConfiguration curInstance : config.getActiveMotors()) {
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
index bce0980f0e..7cf7642617 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java
@@ -35,6 +35,7 @@
import net.sf.openrocket.aerodynamics.AerodynamicCalculator;
import net.sf.openrocket.aerodynamics.BarrowmanCalculator;
import net.sf.openrocket.aerodynamics.FlightConditions;
+import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.logging.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
@@ -439,8 +440,7 @@ public void stateChanged(ChangeEvent e) {
JPanel bottomRow = new JPanel(new MigLayout("fill, gapy 0, ins 0"));
//// Click to select Shift+click to select other Double-click to edit Click+drag to move
- infoMessage = new JLabel(trans.get("RocketPanel.lbl.infoMessage"));
- infoMessage.setFont(new Font("Sans Serif", Font.PLAIN, 9));
+ infoMessage = new StyledLabel(trans.get("RocketPanel.lbl.infoMessage"), -3);
bottomRow.add(infoMessage);
//// Show warnings
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
index db7ebad826..e30bfe8407 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleScrollPane.java
@@ -25,6 +25,7 @@
import net.sf.openrocket.gui.adaptors.DoubleModel;
import net.sf.openrocket.gui.components.UnitSelector;
+import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.unit.Tick;
import net.sf.openrocket.unit.Unit;
import net.sf.openrocket.unit.UnitGroup;
@@ -398,7 +399,7 @@ protected void paintComponent(Graphics g) {
}
// Set color & hints
- g2.setColor(Color.BLACK);
+ g2.setColor(GUIUtil.getUITheme().getTextColor());
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_NORMALIZE);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
index c216b4f509..27de23c005 100644
--- a/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
+++ b/swing/src/net/sf/openrocket/gui/scalefigure/ScaleSelector.java
@@ -13,7 +13,7 @@
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.util.Icons;
-import net.sf.openrocket.gui.widgets.SelectColorButton;
+import net.sf.openrocket.gui.widgets.IconButton;
import net.sf.openrocket.util.StateChangeListener;
@SuppressWarnings("serial")
@@ -41,7 +41,7 @@ public ScaleSelector(ScaleScrollPane scroll) {
this.scrollPane = scroll;
// Zoom out button
- zoomOutButton = new SelectColorButton(Icons.ZOOM_OUT);
+ zoomOutButton = new IconButton(Icons.ZOOM_OUT);
zoomOutButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@@ -93,7 +93,7 @@ public void stateChanged(EventObject e) {
});
// Zoom in button
- zoomInButton = new SelectColorButton(Icons.ZOOM_IN);
+ zoomInButton = new IconButton(Icons.ZOOM_IN);
zoomInButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
index e105af540b..82e49ff9a9 100644
--- a/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
+++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationOptionsPanel.java
@@ -207,6 +207,8 @@ public void actionPerformed(ActionEvent ev) {
currentExtensions = new JPanel(new MigLayout("fillx, gap 0 0, ins 0"));
JScrollPane scroll = new JScrollPane(currentExtensions);
+ currentExtensions.setBorder(GUIUtil.getUITheme().getBorder());
+ scroll.setForeground(GUIUtil.getUITheme().getTextColor());
// $%! scroll pane will not honor "growy"...
sub.add(scroll, "growx, growy, h 100%");
diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java
index d926118a9c..ac2f980100 100644
--- a/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java
+++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationPlotPanel.java
@@ -205,7 +205,7 @@ public void itemStateChanged(ItemEvent e) {
//// The data will be plotted in time order even if the X axis type is not time.
simPlotPanelDesc = new DescriptionArea("", 2, -2f, false);
simPlotPanelDesc.setVisible(false);
- simPlotPanelDesc.setForeground(Color.DARK_RED.toAWTColor());
+ simPlotPanelDesc.setForeground(GUIUtil.getUITheme().getDarkWarningColor());
simPlotPanelDesc.setViewportBorder(BorderFactory.createEmptyBorder());
this.add(simPlotPanelDesc, "width 1px, growx 1, wrap unrel");
diff --git a/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java b/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java
index b8bbdd40fe..a91da30c5a 100644
--- a/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java
+++ b/swing/src/net/sf/openrocket/gui/util/BetterListCellRenderer.java
@@ -1,9 +1,9 @@
package net.sf.openrocket.gui.util;
+
import javax.swing.DefaultListCellRenderer;
import javax.swing.JLabel;
import javax.swing.JList;
-import java.awt.Color;
import java.awt.Component;
/**
@@ -12,6 +12,7 @@
* @author Sibo Van Gool
*/
public class BetterListCellRenderer extends DefaultListCellRenderer {
+
@Override
public Component getListCellRendererComponent(JList> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
@@ -20,16 +21,16 @@ public Component getListCellRendererComponent(JList> list, Object value, int i
// Alternating row colors
if (!isSelected) {
if (index % 2 == 0) {
- label.setBackground(Color.WHITE);
+ label.setBackground(GUIUtil.getUITheme().getRowBackgroundDarkerColor());
} else {
- label.setBackground(new Color(245, 245, 245));
+ label.setBackground(GUIUtil.getUITheme().getRowBackgroundLighterColor());
}
}
// Text color
if (isSelected) {
- label.setForeground(Color.WHITE);
+ label.setForeground(GUIUtil.getUITheme().getTextSelectionForegroundColor());
} else {
- label.setForeground(Color.BLACK);
+ label.setForeground(GUIUtil.getUITheme().getTextColor());
}
return label;
}
diff --git a/swing/src/net/sf/openrocket/gui/util/GUIUtil.java b/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
index af1d3d16d0..4fac6edd7a 100644
--- a/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
+++ b/swing/src/net/sf/openrocket/gui/util/GUIUtil.java
@@ -64,9 +64,13 @@
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;
+import com.github.weisj.darklaf.LafManager;
+import com.github.weisj.darklaf.theme.IntelliJTheme;
+import net.sf.openrocket.arch.SystemInfo;
import net.sf.openrocket.gui.Resettable;
import net.sf.openrocket.logging.Markers;
import net.sf.openrocket.startup.Application;
+import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Invalidatable;
import net.sf.openrocket.util.MemoryManagement;
@@ -76,7 +80,7 @@
public class GUIUtil {
private static final Logger log = LoggerFactory.getLogger(GUIUtil.class);
-
+
private static final KeyStroke ESCAPE = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
private static final String CLOSE_ACTION_KEY = "escape:WINDOW_CLOSING";
@@ -252,8 +256,24 @@ public void windowClosed(WindowEvent e) {
}
});
}
+
+ /**
+ * Get the current theme used for the UI.
+ * @return the current theme
+ */
+ public static UITheme.Theme getUITheme() {
+ Preferences prefs = Application.getPreferences();
+ Object theme = prefs.getUITheme();
+ if (theme instanceof UITheme.Theme) {
+ return (UITheme.Theme) theme;
+ }
+ return UITheme.Themes.LIGHT;
+ }
-
+ public static void applyLAF() {
+ UITheme.Theme theme = getUITheme();
+ theme.applyTheme();
+ }
/**
* Set the best available look-and-feel into use.
@@ -265,8 +285,13 @@ public static void setBestLAF() {
* other alternatives.
*/
try {
- // Set system L&F
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ // Linux systems often default to a dark mode LAF, so explicitly use light mode
+ if (SystemInfo.getPlatform() == SystemInfo.Platform.UNIX) {
+ LafManager.install(new IntelliJTheme());
+ } else {
+ // Set system L&F
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
// Check whether we have an ugly L&F
LookAndFeel laf = UIManager.getLookAndFeel();
@@ -295,6 +320,15 @@ public static void setBestLAF() {
}
// Set the select foreground for buttons to not be black on a blue background
UIManager.put("Button.selectForeground", Color.WHITE);
+
+ // Fix some UI bugs on macOS
+ if (SystemInfo.getPlatform() == SystemInfo.Platform.MAC_OS) {
+ // Set the foreground of active tabs to black; there was a bug where you had a white background and white foreground
+ UIManager.put("TabbedPane.foreground", Color.black);
+
+ // Set the select foreground for buttons to not be black on a blue background
+ UIManager.put("ToggleButton.selectForeground", Color.WHITE);
+ }
} catch (Exception e) {
log.warn("Error setting LAF: " + e);
}
diff --git a/swing/src/net/sf/openrocket/gui/util/Icons.java b/swing/src/net/sf/openrocket/gui/util/Icons.java
index 85f415983e..2d8a34ac43 100644
--- a/swing/src/net/sf/openrocket/gui/util/Icons.java
+++ b/swing/src/net/sf/openrocket/gui/util/Icons.java
@@ -94,13 +94,19 @@ public class Icons {
public static final Icon NOT_FAVORITE = loadImageIcon("pix/icons/star_silver.png", "Not favorite");
public static final Icon FAVORITE = loadImageIcon("pix/icons/star_gold.png", "Favorite");
-
- public static final Icon CG_OVERRIDE = loadImageIcon("pix/icons/cg-override.png", "CG Override");
- public static final Icon CG_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/cg-override-subcomponent.png", "CG Override Subcomponent");
- public static final Icon CD_OVERRIDE = loadImageIcon("pix/icons/cd-override.png", "CD Override");
- public static final Icon CD_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/cd-override-subcomponent.png", "CD Override Subcomponent");
- public static final Icon MASS_OVERRIDE = loadImageIcon("pix/icons/mass-override.png", "Mass Override");
- public static final Icon MASS_OVERRIDE_SUBCOMPONENT = loadImageIcon("pix/icons/mass-override-subcomponent.png", "Mass Override Subcomponent");
+
+ public static final Icon MASS_OVERRIDE_LIGHT = loadImageIcon("pix/icons/mass-override_light.png", "Mass Override");
+ public static final Icon MASS_OVERRIDE_DARK = loadImageIcon("pix/icons/mass-override_dark.png", "Mass Override");
+ public static final Icon MASS_OVERRIDE_SUBCOMPONENT_LIGHT = loadImageIcon("pix/icons/mass-override-subcomponent_light.png", "Mass Override Subcomponent");
+ public static final Icon MASS_OVERRIDE_SUBCOMPONENT_DARK = loadImageIcon("pix/icons/mass-override-subcomponent_dark.png", "Mass Override Subcomponent");
+ public static final Icon CG_OVERRIDE_LIGHT = loadImageIcon("pix/icons/cg-override_light.png", "CG Override");
+ public static final Icon CG_OVERRIDE_DARK = loadImageIcon("pix/icons/cg-override_dark.png", "CG Override");
+ public static final Icon CG_OVERRIDE_SUBCOMPONENT_LIGHT = loadImageIcon("pix/icons/cg-override-subcomponent_light.png", "CG Override Subcomponent");
+ public static final Icon CG_OVERRIDE_SUBCOMPONENT_DARK = loadImageIcon("pix/icons/cg-override-subcomponent_dark.png", "CG Override Subcomponent");
+ public static final Icon CD_OVERRIDE_LIGHT = loadImageIcon("pix/icons/cd-override_light.png", "CD Override");
+ public static final Icon CD_OVERRIDE_DARK = loadImageIcon("pix/icons/cd-override_dark.png", "CD Override");
+ public static final Icon CD_OVERRIDE_SUBCOMPONENT_LIGHT = loadImageIcon("pix/icons/cd-override-subcomponent_light.png", "CD Override Subcomponent");
+ public static final Icon CD_OVERRIDE_SUBCOMPONENT_DARK = loadImageIcon("pix/icons/cd-override-subcomponent_dark.png", "CD Override Subcomponent");
// MANUFACTURERS ICONS
public static final Icon RASAERO = loadImageIcon("pix/icons/RASAero_16.png", "RASAero Icon");
diff --git a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
index 7e9db6af13..004895c02f 100644
--- a/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
+++ b/swing/src/net/sf/openrocket/gui/util/SwingPreferences.java
@@ -12,6 +12,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -20,6 +21,17 @@
import java.util.prefs.Preferences;
import net.sf.openrocket.communication.AssetHandler.UpdatePlatform;
+import net.sf.openrocket.rocketcomponent.BodyComponent;
+import net.sf.openrocket.rocketcomponent.FinSet;
+import net.sf.openrocket.rocketcomponent.InternalComponent;
+import net.sf.openrocket.rocketcomponent.LaunchLug;
+import net.sf.openrocket.rocketcomponent.MassObject;
+import net.sf.openrocket.rocketcomponent.ParallelStage;
+import net.sf.openrocket.rocketcomponent.PodSet;
+import net.sf.openrocket.rocketcomponent.RailButton;
+import net.sf.openrocket.rocketcomponent.RecoveryDevice;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.TubeFinSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -46,6 +58,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public static final String NODE_WINDOWS = "windows";
public static final String NODE_TABLES = "tables";
+ private static final String UI_FONT_SIZE = "UIFontSize";
public static final String UPDATE_PLATFORM = "UpdatePlatform";
private static final List SUPPORTED_LOCALES;
@@ -58,6 +71,8 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
list.add(new Locale("uk", "UA"));
SUPPORTED_LOCALES = Collections.unmodifiableList(list);
}
+
+ private final HashMap, String> DEFAULT_COLORS = new HashMap<>();
/**
@@ -94,8 +109,22 @@ public SwingPreferences() {
}
}
PREFNODE = root.node(NODENAME);
+ fillDefaultComponentColors();
}
-
+
+ private void fillDefaultComponentColors() {
+ DEFAULT_COLORS.put(BodyComponent.class, getUIThemeAsTheme().getDefaultBodyComponentColor());
+ DEFAULT_COLORS.put(TubeFinSet.class, getUIThemeAsTheme().getDefaultTubeFinSetColor());
+ DEFAULT_COLORS.put(FinSet.class, getUIThemeAsTheme().getDefaultFinSetColor());
+ DEFAULT_COLORS.put(LaunchLug.class, getUIThemeAsTheme().getDefaultLaunchLugColor());
+ DEFAULT_COLORS.put(RailButton.class, getUIThemeAsTheme().getDefaultRailButtonColor());
+ DEFAULT_COLORS.put(InternalComponent.class, getUIThemeAsTheme().getDefaultInternalComponentColor());
+ DEFAULT_COLORS.put(MassObject.class, getUIThemeAsTheme().getDefaultMassObjectColor());
+ DEFAULT_COLORS.put(RecoveryDevice.class, getUIThemeAsTheme().getDefaultRecoveryDeviceColor());
+ DEFAULT_COLORS.put(PodSet.class, getUIThemeAsTheme().getDefaultPodSetColor());
+ DEFAULT_COLORS.put(ParallelStage.class, getUIThemeAsTheme().getDefaultParallelStageColor());
+ }
+
public String getNodename() {
return NODENAME;
}
@@ -297,6 +326,82 @@ public Preferences getNode(String nodeName) {
public static List getSupportedLocales() {
return SUPPORTED_LOCALES;
}
+
+ /**
+ * Get the current theme used for the UI.
+ * @return the current theme
+ */
+ @Override
+ public Object getUITheme() {
+ return getUIThemeAsTheme();
+ }
+
+ private UITheme.Theme getUIThemeAsTheme() {
+ String themeName = getString(net.sf.openrocket.startup.Preferences.UI_THEME, UITheme.Themes.LIGHT.name());
+ if (themeName == null) return UITheme.Themes.LIGHT; // Default theme
+ return UITheme.Themes.valueOf(themeName);
+ }
+
+ /**
+ * Set the theme used for the UI.
+ * @param theme the theme to set
+ */
+ @Override
+ public void setUITheme(Object theme) {
+ if (!(theme instanceof UITheme.Theme)) return;
+ putString(net.sf.openrocket.startup.Preferences.UI_THEME, ((UITheme.Theme) theme).name());
+ storeVersion();
+ }
+
+ /**
+ * Get the current font size used for the UI.
+ * @return the current font size
+ */
+ public int getUIFontSize() {
+ return getInt(UI_FONT_SIZE, getDefaultFontSize());
+ }
+
+ public final float getRocketInfoFontSize() {
+ return (float) ((getUIFontSize() - 2) + 3 * Application.getPreferences().getChoice(net.sf.openrocket.startup.Preferences.ROCKET_INFO_FONT_SIZE, 2, 0));
+ }
+
+ private static int getDefaultFontSize() {
+ javax.swing.UIDefaults uiDefaults = javax.swing.UIManager.getDefaults();
+ Object value = uiDefaults.get("defaultFont");
+ if (value instanceof javax.swing.plaf.FontUIResource fontUIResource) {
+ return fontUIResource.getSize();
+ } else {
+ return 12;
+ }
+ }
+
+ /**
+ * Set the font size used for the UI.
+ * @param size the font size to set
+ */
+ public void setUIFontSize(int size) {
+ putInt(UI_FONT_SIZE, size);
+ storeVersion();
+ }
+
+ public net.sf.openrocket.util.Color getDefaultColor(Class extends RocketComponent> c) {
+ String color = get("componentColors", c, DEFAULT_COLORS);
+ if (color == null)
+ return net.sf.openrocket.util.Color.fromAWTColor(getUIThemeAsTheme().getTextColor());
+
+ net.sf.openrocket.util.Color clr = parseColor(color);
+ if (clr != null) {
+ return clr;
+ } else {
+ return net.sf.openrocket.util.Color.fromAWTColor(getUIThemeAsTheme().getTextColor());
+ }
+ }
+
+ public final void setDefaultColor(Class extends RocketComponent> c, net.sf.openrocket.util.Color color) {
+ if (color == null)
+ return;
+ putString("componentColors", c.getSimpleName(), stringifyColor(color));
+ }
public File getDefaultDirectory() {
String file = getString(net.sf.openrocket.startup.Preferences.DEFAULT_DIRECTORY, null);
@@ -432,18 +537,6 @@ public void setUserThrustCurveFiles(List files) {
putString(USER_THRUST_CURVES_KEY, str);
}
- public Color getMotorBorderColor() {
- // TODO: MEDIUM: Motor color (settable?)
- return new Color(0, 0, 0, 200);
- }
-
-
- public Color getMotorFillColor() {
- // TODO: MEDIUM: Motor fill color (settable?)
- return new Color(0, 0, 0, 100);
- }
-
-
public static int getMaxThreadCount() {
return Runtime.getRuntime().availableProcessors();
}
diff --git a/swing/src/net/sf/openrocket/gui/util/UITheme.java b/swing/src/net/sf/openrocket/gui/util/UITheme.java
new file mode 100644
index 0000000000..43805d8700
--- /dev/null
+++ b/swing/src/net/sf/openrocket/gui/util/UITheme.java
@@ -0,0 +1,572 @@
+package net.sf.openrocket.gui.util;
+
+import com.github.weisj.darklaf.LafManager;
+import com.github.weisj.darklaf.theme.DarculaTheme;
+import net.sf.openrocket.l10n.Translator;
+import net.sf.openrocket.startup.Application;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import java.awt.Color;
+import java.awt.Font;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+public class UITheme {
+ private static final Translator trans = Application.getTranslator();
+ private static final Logger log = LoggerFactory.getLogger(UITheme.class);
+
+ public interface Theme {
+ void applyTheme();
+ String name(); // Provided by enum, gives the name of the enum constant
+ String getDisplayName();
+ Color getBackgroundColor();
+ Color getBorderColor();
+ Color getTextColor();
+ Color getDimTextColor();
+ Color getTextSelectionForegroundColor();
+ Color getTextSelectionBackgroundColor();
+ Color getWarningColor();
+ Color getDarkWarningColor();
+ Color getRowBackgroundLighterColor();
+ Color getRowBackgroundDarkerColor();
+ Color getFlightDataTextActiveColor();
+ Color getFlightDataTextInactiveColor();
+
+ // Component colors
+ String getDefaultBodyComponentColor();
+ String getDefaultTubeFinSetColor();
+ String getDefaultFinSetColor();
+ String getDefaultLaunchLugColor();
+ String getDefaultRailButtonColor();
+ String getDefaultInternalComponentColor();
+ String getDefaultMassObjectColor();
+ String getDefaultRecoveryDeviceColor();
+ String getDefaultPodSetColor();
+ String getDefaultParallelStageColor();
+
+ Color getMotorBorderColor();
+ Color getMotorFillColor();
+
+ Color getCGColor();
+ Color getCPColor();
+
+ Color getURLColor();
+
+ Color getComponentTreeBackgroundColor();
+ Color getComponentTreeForegroundColor();
+
+ Color getFinPointGridMajorLineColor();
+ Color getFinPointGridMinorLineColor();
+ Color getFinPointPointColor();
+ Color getFinPointSelectedPointColor();
+ Color getFinPointBodyLineColor();
+
+ Icon getMassOverrideIcon();
+ Icon getMassOverrideSubcomponentIcon();
+ Icon getCGOverrideIcon();
+ Icon getCGOverrideSubcomponentIcon();
+ Icon getCDOverrideIcon();
+ Icon getCDOverrideSubcomponentIcon();
+
+ Border getBorder();
+
+ void formatScriptTextArea(RSyntaxTextArea textArea);
+ }
+
+ public enum Themes implements Theme {
+ LIGHT {
+ private final String displayName = trans.get("UITheme.Light");
+
+ @Override
+ public void applyTheme() {
+ final SwingPreferences prefs = (SwingPreferences) Application.getPreferences();
+
+ GUIUtil.setBestLAF();
+ setGlobalFontSize(prefs.getUIFontSize());
+ }
+
+ @Override
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ public Color getBackgroundColor() {
+ return Color.WHITE;
+ }
+
+ @Override
+ public Color getBorderColor() {
+ return Color.BLACK;
+ }
+
+ @Override
+ public Color getTextColor() {
+ return Color.BLACK;
+ }
+
+ @Override
+ public Color getDimTextColor() {
+ return Color.GRAY;
+ }
+
+ @Override
+ public Color getTextSelectionForegroundColor() {
+ return UIManager.getColor("Tree.selectionForeground");
+ }
+
+ @Override
+ public Color getTextSelectionBackgroundColor() {
+ return UIManager.getColor("Tree.selectionBackground");
+ }
+
+ @Override
+ public Color getWarningColor() {
+ return Color.RED;
+ }
+
+ @Override
+ public Color getDarkWarningColor() {
+ return new Color(200,0,0);
+ }
+
+ @Override
+ public Color getRowBackgroundLighterColor() {
+ return Color.WHITE;
+ }
+
+ @Override
+ public Color getRowBackgroundDarkerColor() {
+ return new Color(245, 245, 245);
+ }
+
+ @Override
+ public Color getFlightDataTextActiveColor() {
+ return new Color(0,0,127);
+ }
+
+ @Override
+ public Color getFlightDataTextInactiveColor() {
+ return new Color(0,0,127,127);
+ }
+
+ @Override
+ public String getDefaultBodyComponentColor() {
+ return "0,0,240";
+ }
+ @Override
+ public String getDefaultTubeFinSetColor() {
+ return "0,0,200";
+ }
+ @Override
+ public String getDefaultFinSetColor() {
+ return "0,0,200";
+ }
+ @Override
+ public String getDefaultLaunchLugColor() {
+ return "0,0,180";
+ }
+ @Override
+ public String getDefaultRailButtonColor() {
+ return "0,0,180";
+ }
+ @Override
+ public String getDefaultInternalComponentColor() {
+ return "170,0,100";
+ }
+ @Override
+ public String getDefaultMassObjectColor() {
+ return "0,0,0";
+ }
+ @Override
+ public String getDefaultRecoveryDeviceColor() {
+ return "255,0,0";
+ }
+ @Override
+ public String getDefaultPodSetColor() {
+ return "160,160,215";
+ }
+ @Override
+ public String getDefaultParallelStageColor() {
+ return "198,163,184";
+ }
+
+ @Override
+ public Color getMotorBorderColor() {
+ return new Color(0, 0, 0, 200);
+ }
+
+ @Override
+ public Color getMotorFillColor() {
+ return new Color(0, 0, 0, 100);
+ }
+
+ @Override
+ public Color getCGColor() {
+ return Color.BLUE;
+ }
+
+ @Override
+ public Color getCPColor() {
+ return Color.RED;
+ }
+
+ @Override
+ public Color getURLColor() {
+ return Color.BLUE;
+ }
+
+ @Override
+ public Color getComponentTreeBackgroundColor() {
+ return UIManager.getColor("Tree.textBackground");
+ }
+
+ @Override
+ public Color getComponentTreeForegroundColor() {
+ return UIManager.getColor("Tree.textForeground");
+ }
+
+ @Override
+ public Color getFinPointGridMajorLineColor() {
+ return new Color( 0, 0, 255, 80);
+ }
+
+ @Override
+ public Color getFinPointGridMinorLineColor() {
+ return new Color( 0, 0, 255, 30);
+ }
+
+ @Override
+ public Color getFinPointPointColor() {
+ return new Color(200, 0, 0, 255);
+ }
+
+ @Override
+ public Color getFinPointSelectedPointColor() {
+ return new Color(200, 0, 0, 255);
+ }
+
+ @Override
+ public Color getFinPointBodyLineColor() {
+ return Color.BLACK;
+ }
+
+ @Override
+ public Icon getMassOverrideIcon() {
+ return Icons.MASS_OVERRIDE_LIGHT;
+ }
+
+ @Override
+ public Icon getMassOverrideSubcomponentIcon() {
+ return Icons.MASS_OVERRIDE_SUBCOMPONENT_LIGHT;
+ }
+
+ @Override
+ public Icon getCGOverrideIcon() {
+ return Icons.CG_OVERRIDE_LIGHT;
+ }
+
+ @Override
+ public Icon getCGOverrideSubcomponentIcon() {
+ return Icons.CG_OVERRIDE_SUBCOMPONENT_LIGHT;
+ }
+
+ @Override
+ public Icon getCDOverrideIcon() {
+ return Icons.CD_OVERRIDE_LIGHT;
+ }
+
+ @Override
+ public Icon getCDOverrideSubcomponentIcon() {
+ return Icons.CD_OVERRIDE_SUBCOMPONENT_LIGHT;
+ }
+
+ @Override
+ public Border getBorder() {
+ return null;
+ }
+
+ @Override
+ public void formatScriptTextArea(RSyntaxTextArea textArea) {
+ try {
+ org.fife.ui.rsyntaxtextarea.Theme theme = org.fife.ui.rsyntaxtextarea.Theme.load(getClass().getResourceAsStream(
+ "/org/fife/ui/rsyntaxtextarea/themes/default.xml"));
+ theme.apply(textArea);
+ textArea.setCurrentLineHighlightColor(new Color(255, 255, 230));
+ } catch (IOException ioe) {
+ log.warn("Unable to load RSyntaxTextArea theme", ioe);
+ }
+ }
+ },
+ DARK {
+ private final String displayName = trans.get("UITheme.Dark");
+ @Override
+ public void applyTheme() {
+ final SwingPreferences prefs = (SwingPreferences) Application.getPreferences();
+
+ LafManager.install(new DarculaTheme());
+ setGlobalFontSize(prefs.getUIFontSize());
+ }
+
+ @Override
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ @Override
+ public Color getBackgroundColor() {
+ return new Color(73, 76, 79);
+ }
+
+ @Override
+ public Color getBorderColor() {
+ return new Color(97, 99, 101);
+ }
+
+ @Override
+ public Color getTextColor() {
+ return UIManager.getColor("Label.foreground");
+ }
+
+ @Override
+ public Color getDimTextColor() {
+ return new Color(162, 162, 162);
+ }
+
+ @Override
+ public Color getTextSelectionForegroundColor() {
+ return Color.WHITE;
+ }
+
+ @Override
+ public Color getTextSelectionBackgroundColor() {
+ return new Color(75, 110, 175);
+ }
+
+ @Override
+ public Color getWarningColor() {
+ return new Color(246, 143, 143);
+ }
+
+ @Override
+ public Color getDarkWarningColor() {
+ return new Color(229, 103, 103);
+ }
+
+ @Override
+ public Color getRowBackgroundLighterColor() {
+ return new Color(65, 69, 71);
+ }
+
+ @Override
+ public Color getRowBackgroundDarkerColor() {
+ return new Color(60, 63, 65);
+ }
+
+ @Override
+ public Color getFlightDataTextActiveColor() {
+ return new Color(145, 183, 231);
+ }
+
+ @Override
+ public Color getFlightDataTextInactiveColor() {
+ return new Color(128, 166, 230, 127);
+ }
+
+ @Override
+ public String getDefaultBodyComponentColor() {
+ return "150,162,255";
+ }
+ @Override
+ public String getDefaultTubeFinSetColor() {
+ return "150,178,255";
+ }
+ @Override
+ public String getDefaultFinSetColor() {
+ return "150,178,255";
+ }
+ @Override
+ public String getDefaultLaunchLugColor() {
+ return "142,153,238";
+ }
+ @Override
+ public String getDefaultRailButtonColor() {
+ return "142,153,238";
+ }
+ @Override
+ public String getDefaultInternalComponentColor() {
+ return "181,128,151";
+ }
+ @Override
+ public String getDefaultMassObjectColor() {
+ return "210,210,210";
+ }
+ @Override
+ public String getDefaultRecoveryDeviceColor() {
+ return "220,90,90";
+ }
+ @Override
+ public String getDefaultPodSetColor() {
+ return "190,190,235";
+ }
+ @Override
+ public String getDefaultParallelStageColor() {
+ return "210,180,195";
+ }
+
+ @Override
+ public Color getMotorBorderColor() {
+ return new Color(0, 0, 0, 100);
+ }
+
+ @Override
+ public Color getMotorFillColor() {
+ return new Color(0, 0, 0, 50);
+ }
+
+ @Override
+ public Color getCGColor() {
+ return new Color(85, 133, 253);
+ }
+
+ @Override
+ public Color getCPColor() {
+ return new Color(255, 72, 106);
+ }
+
+ @Override
+ public Color getURLColor() {
+ return new Color(150, 167, 255);
+ }
+
+ @Override
+ public Color getComponentTreeBackgroundColor() {
+ return getBackgroundColor();
+ }
+
+ @Override
+ public Color getComponentTreeForegroundColor() {
+ return getTextColor();
+ }
+
+ @Override
+ public Color getFinPointGridMajorLineColor() {
+ return new Color(135, 135, 199, 197);
+ }
+
+ @Override
+ public Color getFinPointGridMinorLineColor() {
+ return new Color(121, 121, 189, 69);
+ }
+
+ @Override
+ public Color getFinPointPointColor() {
+ return new Color(217, 108, 108, 255);
+ }
+
+ @Override
+ public Color getFinPointSelectedPointColor() {
+ return new Color(232, 78, 78, 255);
+ }
+
+ @Override
+ public Color getFinPointBodyLineColor() {
+ return Color.WHITE;
+ }
+
+ @Override
+ public Icon getMassOverrideIcon() {
+ return Icons.MASS_OVERRIDE_DARK;
+ }
+
+ @Override
+ public Icon getMassOverrideSubcomponentIcon() {
+ return Icons.MASS_OVERRIDE_SUBCOMPONENT_DARK;
+ }
+
+ @Override
+ public Icon getCGOverrideIcon() {
+ return Icons.CG_OVERRIDE_DARK;
+ }
+
+ @Override
+ public Icon getCGOverrideSubcomponentIcon() {
+ return Icons.CG_OVERRIDE_SUBCOMPONENT_DARK;
+ }
+
+ @Override
+ public Icon getCDOverrideIcon() {
+ return Icons.CD_OVERRIDE_DARK;
+ }
+
+ @Override
+ public Icon getCDOverrideSubcomponentIcon() {
+ return Icons.CD_OVERRIDE_SUBCOMPONENT_DARK;
+ }
+
+ @Override
+ public Border getBorder() {
+ return BorderFactory.createLineBorder(getBorderColor());
+ }
+
+ @Override
+ public void formatScriptTextArea(RSyntaxTextArea textArea) {
+ try {
+ org.fife.ui.rsyntaxtextarea.Theme theme = org.fife.ui.rsyntaxtextarea.Theme.load(getClass().getResourceAsStream(
+ "/org/fife/ui/rsyntaxtextarea/themes/dark.xml"));
+ theme.apply(textArea);
+ } catch (IOException ioe) {
+ log.warn("Unable to load RSyntaxTextArea theme", ioe);
+ }
+ }
+ }
+ }
+
+ private static void setGlobalFontSize(int size) {
+ // Some fonts have different sizes for different components, so we need to adjust them
+ final Map fontOffsets = new HashMap<>();
+ fontOffsets.put("MenuBar.font", 1f);
+ fontOffsets.put("Tree.font", -1f);
+ fontOffsets.put("Slider.font", -2f);
+ fontOffsets.put("TableHeader.font", -2f);
+ fontOffsets.put("ColorChooser.font", -1f);
+ fontOffsets.put("Menu.acceleratorFont", 1f);
+ fontOffsets.put("InternalFrame.optionDialogTitleFont", 1f);
+ fontOffsets.put("InternalFrame.paletteTitleFont", 1f);
+ fontOffsets.put("MenuItem.font", 1f);
+ fontOffsets.put("PopupMenu.font", 1f);
+ fontOffsets.put("MenuItem.acceleratorFont", 1f);
+ fontOffsets.put("RadioButtonMenuItem.font", 1f);
+ fontOffsets.put("Table.font", -1f);
+ //fontOffsets.put("IconButton.font", -2f); // The default doesn't really look nice, we want the normal font size instead
+ fontOffsets.put("InternalFrame.titleFont", 1f);
+ fontOffsets.put("List.font", -1f);
+ fontOffsets.put("RadioButtonMenuItem.acceleratorFont", 1f);
+ fontOffsets.put("CheckBoxMenuItem.acceleratorFont", 1f);
+ fontOffsets.put("Menu.font", 1f);
+ fontOffsets.put("TabbedPane.smallFont", -2f);
+ fontOffsets.put("CheckBoxMenuItem.font", 1f);
+ fontOffsets.put("ToolTip.font", -2f);
+
+ // Iterate over all keys in the UIManager defaults and set the font
+ for (Enumeration