From 50af3d10cce008eb0fffa8feb4b1863c2b9679da Mon Sep 17 00:00:00 2001 From: SiboVG Date: Wed, 18 Sep 2024 06:05:04 +0100 Subject: [PATCH] Use selectable component tree for CA plot/export components selection --- .../main/resources/l10n/messages.properties | 23 +- .../resources/l10n/messages_ar.properties | 14 +- .../resources/l10n/messages_cs.properties | 14 +- .../resources/l10n/messages_de.properties | 14 +- .../resources/l10n/messages_es.properties | 14 +- .../resources/l10n/messages_fr.properties | 14 +- .../resources/l10n/messages_it.properties | 14 +- .../resources/l10n/messages_ja.properties | 14 +- .../resources/l10n/messages_nl.properties | 14 +- .../resources/l10n/messages_pl.properties | 14 +- .../resources/l10n/messages_pt.properties | 14 +- .../resources/l10n/messages_ru.properties | 14 +- .../resources/l10n/messages_uk_UA.properties | 14 +- .../resources/l10n/messages_zh_CN.properties | 14 +- .../componentanalysis/CAExportPanel.java | 497 ++++-------------- .../gui/dialogs/componentanalysis/CAPlot.java | 32 +- .../CAPlotConfiguration.java | 35 +- .../componentanalysis/CAPlotPanel.java | 11 +- .../componentanalysis/CAPlotTypeSelector.java | 133 +++-- .../ComponentAnalysisDialog.java | 11 +- .../ComponentAnalysisPlotExportPanel.java | 12 +- .../ComponentSelectionDialog.java | 85 +++ .../openrocket/swing/gui/main/BasicFrame.java | 2 +- .../componenttree/ComponentTreeRenderer.java | 4 - .../SelectableComponentTree.java | 42 ++ .../SelectableComponentTreeRenderer.java | 78 +++ .../ToggleTreeSelectionModel.java | 53 ++ .../swing/gui/plot/PlotTypeSelector.java | 12 +- .../gui/simulation/SimulationExportPanel.java | 7 +- .../openrocket/swing/gui/theme/UITheme.java | 2 +- .../info/openrocket/swing/gui/util/Icons.java | 11 + .../swing/gui/widgets/CSVExportPanel.java | 454 +++++++--------- 32 files changed, 835 insertions(+), 851 deletions(-) create mode 100644 swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentSelectionDialog.java create mode 100644 swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTree.java create mode 100644 swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTreeRenderer.java create mode 100644 swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ToggleTreeSelectionModel.java diff --git a/core/src/main/resources/l10n/messages.properties b/core/src/main/resources/l10n/messages.properties index 41229c7c3..5ae862f24 100644 --- a/core/src/main/resources/l10n/messages.properties +++ b/core/src/main/resources/l10n/messages.properties @@ -715,8 +715,6 @@ SimulationStepper.error.totalMassZero = Total mass of active states is 0 ! SimulationExportPanel SimExpPan.border.Vartoexport = Variables to export SimExpPan.border.Stage = Stage to export -SimExpPan.but.Selectall = Select all -SimExpPan.but.Selectnone = Select none SimExpPan.border.FormatSettings = Format settings SimExpPan.lbl.Fieldsepstr = Field separator string: SimExpPan.lbl.longA1 = The string used to separate the fields in the exported file.
@@ -737,12 +735,17 @@ SimExpPan.lbl.ttip.Commentchar = The character(s) that mark a comment line. SimExpPan.Fileexists.desc1 = File \" SimExpPan.Fileexists.desc2 = \" exists. Overwrite? SimExpPan.Fileexists.title = File exists -SimExpPan.ExportingVar.desc1 = Exporting 1 variable out of -SimExpPan.ExportingVar.desc2 = Exporting -SimExpPan.ExportingVar.desc3 = variables out of -SimExpPan.Col.Variable = Variable -SimExpPan.Col.Unit = Unit +! CSVExportPanel +CSVExportPanel.but.Selectall = Select all +CSVExportPanel.but.Selectnone = Select none +CSVExportPanel.ExportingVar.desc1 = Exporting 1 variable out of +CSVExportPanel.ExportingVar.desc2 = Exporting +CSVExportPanel.ExportingVar.desc3 = variables out of +CSVExportPanel.lbl.Export = Export +CSVExportPanel.lbl.Variable = Variable +CSVExportPanel.lbl.Unit = Unit +CSVExportPanel.lbl.Extra = Extra CsvOptionPanel.separator.space = SPACE CsvOptionPanel.separator.tab = TAB @@ -980,6 +983,7 @@ CAExportPanel.checkbox.ttip.Includecadesc = Include information at the beginning CAExportPanel.dlg.MissingComponents.txt1 = The following data type(s) have no selected components and will be ignored during exporting: CAExportPanel.dlg.MissingComponents.txt2 = Do you want to continue with the export? CAExportPanel.dlg.MissingComponents.title = No components selected +CAExportPanel.btn.SelectComponents = Select components ! CADataTypeGroup CADataTypeGroup.DOMAIN = Domain Parameter @@ -997,11 +1001,14 @@ CADomainDataType.lbl.rollrate = Roll rate CAPlotConfiguration.TotalCD = Total CD vs. Mach number ! CAPlotTypeSelector -CAPlotTypeSelector.lbl.component = Component: +CAPlotTypeSelector.btn.SelectComponents = Select components ! CAPlotPanel CAPlotPanel.lbl.PlotTitle = Component Analysis Plot +! ComponentSelectionDialog +ComponentSelectionDialog.btn.ConfirmSelection = Confirm selection + ! Custom Material dialog custmatdlg.title.Custommaterial = Custom material custmatdlg.lbl.Materialname = Material name: diff --git a/core/src/main/resources/l10n/messages_ar.properties b/core/src/main/resources/l10n/messages_ar.properties index 190eac1d8..ca25e2b89 100644 --- a/core/src/main/resources/l10n/messages_ar.properties +++ b/core/src/main/resources/l10n/messages_ar.properties @@ -567,8 +567,8 @@ SimulationModifierTree.OptimizationParameters = معايير التحسين ! SimulationExportPanel SimExpPan.border.Vartoexport = متغيرات للتصدير SimExpPan.border.Stage = مرحلة التصدير -SimExpPan.but.Selectall = حدد الكل -SimExpPan.but.Selectnone = لا تختر شيء +CSVExportPanel.but.Selectall = حدد الكل +CSVExportPanel.but.Selectnone = لا تختر شيء SimExpPan.border.FormatSettings = إعدادات التنسيق SimExpPan.lbl.Fieldsepstr = :سلسلة فاصل المجال SimExpPan.lbl.longA1 = .السلسلة المستخدمة لفصل الحقول في الملف المصدر
@@ -589,11 +589,11 @@ SimExpPan.lbl.ttip.Commentchar = .الذي يميز سطر التعليق (ال SimExpPan.Fileexists.desc1 = \" الملف SimExpPan.Fileexists.desc2 = \" موجود. هل تريد الكتابة فوقه؟ SimExpPan.Fileexists.title = الملف موجود -SimExpPan.ExportingVar.desc1 = تصدير متغير واحد من -SimExpPan.ExportingVar.desc2 = تصدير -SimExpPan.ExportingVar.desc3 = المتغيرات من أصل -SimExpPan.Col.Variable = متغيرات -SimExpPan.Col.Unit = وحدة +CSVExportPanel.ExportingVar.desc1 = تصدير متغير واحد من +CSVExportPanel.ExportingVar.desc2 = تصدير +CSVExportPanel.ExportingVar.desc3 = المتغيرات من أصل +CSVExportPanel.lbl.Variable = متغيرات +CSVExportPanel.lbl.Unit = وحدة CsvOptionPanel.separator.space = مفتاح المساحة diff --git a/core/src/main/resources/l10n/messages_cs.properties b/core/src/main/resources/l10n/messages_cs.properties index aafbf6947..28b602379 100644 --- a/core/src/main/resources/l10n/messages_cs.properties +++ b/core/src/main/resources/l10n/messages_cs.properties @@ -411,8 +411,8 @@ RK4SimulationStepper.error.valuesTooLarge = Hodnoty simulace prekrocily limity. ! SimulationExportPanel SimExpPan.border.Vartoexport = Promenn k exportu -SimExpPan.but.Selectall = Oznac v\u0161e -SimExpPan.but.Selectnone = Nic neoznacuj +CSVExportPanel.but.Selectall = Oznac v\u0161e +CSVExportPanel.but.Selectnone = Nic neoznacuj SimExpPan.border.Fieldsep = Oddelovac prvku SimExpPan.lbl.Fieldsepstr = Retezec slou\u017Ec k oddelovn prvku: SimExpPan.lbl.longA1 = Retezec slou\u017Ec k oddelen prvku v exportovanm souboru.
@@ -430,11 +430,11 @@ SimExpPan.but.Exporttofile = Exportuj do souboru... SimExpPan.Fileexists.desc1 = Soubor \" SimExpPan.Fileexists.desc2 = \" existuje. Chcete ho prepsat? SimExpPan.Fileexists.title = Soubor existuje -SimExpPan.ExportingVar.desc1 = Exportuji 1 promennou od -SimExpPan.ExportingVar.desc2 = Exportuji -SimExpPan.ExportingVar.desc3 = promenn od -SimExpPan.Col.Variable = Promenn -SimExpPan.Col.Unit = Jednoty +CSVExportPanel.ExportingVar.desc1 = Exportuji 1 promennou od +CSVExportPanel.ExportingVar.desc2 = Exportuji +CSVExportPanel.ExportingVar.desc3 = promenn od +CSVExportPanel.lbl.Variable = Promenn +CSVExportPanel.lbl.Unit = Jednoty CsvOptionPanel.separator.space = Mezernk diff --git a/core/src/main/resources/l10n/messages_de.properties b/core/src/main/resources/l10n/messages_de.properties index 64465e4b1..9499d643c 100644 --- a/core/src/main/resources/l10n/messages_de.properties +++ b/core/src/main/resources/l10n/messages_de.properties @@ -414,8 +414,8 @@ RK4SimulationStepper.error.valuesTooLarge = Simulationswerte ! SimulationExportPanel SimExpPan.border.Vartoexport = zu exportierende Variablen -SimExpPan.but.Selectall = Alle auswhlen -SimExpPan.but.Selectnone = Keine auswhlen +CSVExportPanel.but.Selectall = Alle auswhlen +CSVExportPanel.but.Selectnone = Keine auswhlen SimExpPan.border.Fieldsep = Feldtrennung SimExpPan.lbl.Fieldsepstr = Trennzeichen SimExpPan.lbl.longA1 = Das Trennzeichen wird benutzt, um die Felder in der exportierten Datei voneinander zu trennen.
@@ -433,11 +433,11 @@ SimExpPan.but.Exporttofile = In Datei exportieren... SimExpPan.Fileexists.desc1 = File \"",Datei \" SimExpPan.Fileexists.desc2 = \" existiert bereits. berschreiben?" SimExpPan.Fileexists.title = Datei existiert bereits -SimExpPan.ExportingVar.desc1 = Exportiere Variable 1 aus -SimExpPan.ExportingVar.desc2 = Exportiere -SimExpPan.ExportingVar.desc3 = Variablen aus -SimExpPan.Col.Variable = Variable -SimExpPan.Col.Unit = Einheit +CSVExportPanel.ExportingVar.desc1 = Exportiere Variable 1 aus +CSVExportPanel.ExportingVar.desc2 = Exportiere +CSVExportPanel.ExportingVar.desc3 = Variablen aus +CSVExportPanel.lbl.Variable = Variable +CSVExportPanel.lbl.Unit = Einheit CsvOptionPanel.separator.space = LEER diff --git a/core/src/main/resources/l10n/messages_es.properties b/core/src/main/resources/l10n/messages_es.properties index c9a053ac1..61fedf408 100644 --- a/core/src/main/resources/l10n/messages_es.properties +++ b/core/src/main/resources/l10n/messages_es.properties @@ -969,11 +969,11 @@ ShockCordCfg.lbl.plus = Localizaci\u00f3n: ShockCordCfg.tab.General = General ShockCordCfg.tab.ttip.General = Propiedades generales -SimExpPan.Col.Unit = Unidad -SimExpPan.Col.Variable = Variable -SimExpPan.ExportingVar.desc1 = Exportar variables -SimExpPan.ExportingVar.desc2 = Exportar -SimExpPan.ExportingVar.desc3 = variables de +CSVExportPanel.lbl.Unit = Unidad +CSVExportPanel.lbl.Variable = Variable +CSVExportPanel.ExportingVar.desc1 = Exportar variables +CSVExportPanel.ExportingVar.desc2 = Exportar +CSVExportPanel.ExportingVar.desc3 = variables de SimExpPan.Fileexists.desc1 = Archivo " SimExpPan.Fileexists.desc2 = " ya existe. \u00bfDesea sobrescribirlo? SimExpPan.Fileexists.title = El archivo ya existe @@ -982,8 +982,8 @@ SimExpPan.border.Fieldsep = Separador de campo SimExpPan.border.Stage = Etapa a exportar SimExpPan.border.Vartoexport = Variables para exportar SimExpPan.but.Exporttofile = Exportar al documento ... -SimExpPan.but.Selectall = Seleccionar todo -SimExpPan.but.Selectnone = No seleccionar nada +CSVExportPanel.but.Selectall = Seleccionar todo +CSVExportPanel.but.Selectnone = No seleccionar nada SimExpPan.checkbox.Incflightevents = Incluir los eventos del vuelo. SimExpPan.checkbox.Includefielddesc = Incluir descripciones del campo. SimExpPan.checkbox.Includesimudesc = Incluir descripci\u00f3n de la simulaci\u00f3n. diff --git a/core/src/main/resources/l10n/messages_fr.properties b/core/src/main/resources/l10n/messages_fr.properties index 7e71043be..9b3e50229 100644 --- a/core/src/main/resources/l10n/messages_fr.properties +++ b/core/src/main/resources/l10n/messages_fr.properties @@ -959,11 +959,11 @@ ShockCordCfg.lbl.plus = plus ShockCordCfg.tab.General = G\u00E9n\u00E9ral ShockCordCfg.tab.ttip.General = Propri\u00E9t\u00E9s g\u00E9n\u00E9rales -SimExpPan.Col.Unit = Unit\u00E9 -SimExpPan.Col.Variable = Variable -SimExpPan.ExportingVar.desc1 = Exporter 1 variable sur un total de -SimExpPan.ExportingVar.desc2 = Exportation -SimExpPan.ExportingVar.desc3 = variable sur un total de +CSVExportPanel.lbl.Unit = Unit\u00E9 +CSVExportPanel.lbl.Variable = Variable +CSVExportPanel.ExportingVar.desc1 = Exporter 1 variable sur un total de +CSVExportPanel.ExportingVar.desc2 = Exportation +CSVExportPanel.ExportingVar.desc3 = variable sur un total de SimExpPan.Fileexists.desc1 = Le fichier " SimExpPan.Fileexists.desc2 = " existe d\u00E9j\u00E0. Ecraser? SimExpPan.Fileexists.title = Le fichier existe @@ -972,8 +972,8 @@ SimExpPan.border.Fieldsep = S\u00E9parateur de champ SimExpPan.border.Stage = Etage \u00E0 exporter SimExpPan.border.Vartoexport = Variables \u00E0 exporter SimExpPan.but.Exporttofile = Exporter dans un fichier... -SimExpPan.but.Selectall = Tout s\u00E9lectionner -SimExpPan.but.Selectnone = Ne rien s\u00E9lectionner +CSVExportPanel.but.Selectall = Tout s\u00E9lectionner +CSVExportPanel.but.Selectnone = Ne rien s\u00E9lectionner SimExpPan.checkbox.Incflightevents = Inclure les \u00E9v\u00E9nements de vol SimExpPan.checkbox.Includefielddesc = Inclure les descriptions des champs SimExpPan.checkbox.Includesimudesc = Inclure la description de la simulation diff --git a/core/src/main/resources/l10n/messages_it.properties b/core/src/main/resources/l10n/messages_it.properties index 6c7e1015e..950a6e7ce 100644 --- a/core/src/main/resources/l10n/messages_it.properties +++ b/core/src/main/resources/l10n/messages_it.properties @@ -417,8 +417,8 @@ RK4SimulationStepper.error.valuesTooLarge = I valori di simulazione anno eccedut ! SimulationExportPanel SimExpPan.border.Vartoexport = Variabili da esportare -SimExpPan.but.Selectall = Seleziona tutto -SimExpPan.but.Selectnone = Deseleziona tutto +CSVExportPanel.but.Selectall = Seleziona tutto +CSVExportPanel.but.Selectnone = Deseleziona tutto SimExpPan.border.Fieldsep = Separatore di campo SimExpPan.lbl.Fieldsepstr = Stringa di separatore di campo: SimExpPan.lbl.longA1 = La stringa usata per separare i campi nel file esportato.
@@ -436,11 +436,11 @@ SimExpPan.but.Exporttofile = Esporta nel file... SimExpPan.Fileexists.desc1 = Il file \" SimExpPan.Fileexists.desc2 = \" esiste. Sovrascrivo? SimExpPan.Fileexists.title = Il file esiste -SimExpPan.ExportingVar.desc1 = Sto esportando 1 variabile su -SimExpPan.ExportingVar.desc2 = Sto esportando -SimExpPan.ExportingVar.desc3 = variabili su -SimExpPan.Col.Variable = Variabile -SimExpPan.Col.Unit = Unita' +CSVExportPanel.ExportingVar.desc1 = Sto esportando 1 variabile su +CSVExportPanel.ExportingVar.desc2 = Sto esportando +CSVExportPanel.ExportingVar.desc3 = variabili su +CSVExportPanel.lbl.Variable = Variabile +CSVExportPanel.lbl.Unit = Unita' CsvOptionPanel.separator.space = SPAZIO diff --git a/core/src/main/resources/l10n/messages_ja.properties b/core/src/main/resources/l10n/messages_ja.properties index dda06596a..b1e5c1c7c 100644 --- a/core/src/main/resources/l10n/messages_ja.properties +++ b/core/src/main/resources/l10n/messages_ja.properties @@ -422,8 +422,8 @@ SimulationModifierTree.OptimizationParameters = \u6700\u9069\u5316\u30D1\u30E9\ ! SimulationExportPanel SimExpPan.border.Vartoexport = \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u3059\u308B\u5909\u6570 -SimExpPan.but.Selectall = \u5168\u3066\u9078\u629E -SimExpPan.but.Selectnone = \u5168\u3066\u975E\u9078\u629E +CSVExportPanel.but.Selectall = \u5168\u3066\u9078\u629E +CSVExportPanel.but.Selectnone = \u5168\u3066\u975E\u9078\u629E SimExpPan.border.Fieldsep = \u533A\u5207\u308A\u6587\u5B57 SimExpPan.lbl.Fieldsepstr = \u533A\u5207\u308A\u6587\u5B57\uFF1A SimExpPan.lbl.longA1 = \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8\u30D5\u30A1\u30A4\u30EB\u3067\u306E\u533A\u5207\u308A\u6587\u5B57
@@ -441,11 +441,11 @@ SimExpPan.but.Exporttofile = \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8 SimExpPan.Fileexists.desc1 = \u30D5\u30A1\u30A4\u30EB \" SimExpPan.Fileexists.desc2 = \" \u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059\u3002\u4E0A\u66F8\u304D\u3057\u307E\u3059\u304B\uFF1F SimExpPan.Fileexists.title = \u30D5\u30A1\u30A4\u30EB\u306E\u4E0A\u66F8\u304D -SimExpPan.ExportingVar.desc1 = Exporting 1 variable out of -SimExpPan.ExportingVar.desc2 = \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8 -SimExpPan.ExportingVar.desc3 = \u5909\u6570\u3001\u5168\u4F53\u306E\u5909\u6570 -SimExpPan.Col.Variable = \u5909\u6570 -SimExpPan.Col.Unit = \u5358\u4F4D +CSVExportPanel.ExportingVar.desc1 = Exporting 1 variable out of +CSVExportPanel.ExportingVar.desc2 = \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8 +CSVExportPanel.ExportingVar.desc3 = \u5909\u6570\u3001\u5168\u4F53\u306E\u5909\u6570 +CSVExportPanel.lbl.Variable = \u5909\u6570 +CSVExportPanel.lbl.Unit = \u5358\u4F4D CsvOptionPanel.separator.space = SPACE diff --git a/core/src/main/resources/l10n/messages_nl.properties b/core/src/main/resources/l10n/messages_nl.properties index a8cdf96f2..690af23a8 100644 --- a/core/src/main/resources/l10n/messages_nl.properties +++ b/core/src/main/resources/l10n/messages_nl.properties @@ -541,8 +541,8 @@ SimulationModifierTree.OptimizationParameters = Optimalisatieparameters ! SimulationExportPanel SimExpPan.border.Vartoexport = Variabelen om te exporteren SimExpPan.border.Stage = Trap om te exporteren -SimExpPan.but.Selectall = Selecteer alles -SimExpPan.but.Selectnone = Selecteer niets +CSVExportPanel.but.Selectall = Selecteer alles +CSVExportPanel.but.Selectnone = Selecteer niets SimExpPan.border.Fieldsep = Veldscheider SimExpPan.lbl.Fieldsepstr = Veldscheidersteken: SimExpPan.lbl.longA1 = De tekenreeks die wordt gebruikt om de velden in het geëxporteerde bestand van elkaar te scheiden.
@@ -559,11 +559,11 @@ SimExpPan.lbl.ttip.Commentchar = Het teken of de tekens die een opmerkingregel m SimExpPan.Fileexists.desc1 = Bestand \" SimExpPan.Fileexists.desc2 = \" bestaat reeds. Overschrijven? SimExpPan.Fileexists.title = Bestand bestaat reeds -SimExpPan.ExportingVar.desc1 = Exporteren van 1 variabele uit -SimExpPan.ExportingVar.desc2 = Exporteren -SimExpPan.ExportingVar.desc3 = variabelen uit -SimExpPan.Col.Variable = Variabele -SimExpPan.Col.Unit = Eenheid +CSVExportPanel.ExportingVar.desc1 = Exporteren van 1 variabele uit +CSVExportPanel.ExportingVar.desc2 = Exporteren +CSVExportPanel.ExportingVar.desc3 = variabelen uit +CSVExportPanel.lbl.Variable = Variabele +CSVExportPanel.lbl.Unit = Eenheid CsvOptionPanel.separator.space = SPACE diff --git a/core/src/main/resources/l10n/messages_pl.properties b/core/src/main/resources/l10n/messages_pl.properties index 895e10dd4..4428b15fd 100644 --- a/core/src/main/resources/l10n/messages_pl.properties +++ b/core/src/main/resources/l10n/messages_pl.properties @@ -414,8 +414,8 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s. ! SimulationExportPanel SimExpPan.border.Vartoexport = Zmienne do wyeksportowania - SimExpPan.but.Selectall = Wybierz wszystko - SimExpPan.but.Selectnone = Odznacz wszystko + CSVExportPanel.but.Selectall = Wybierz wszystko + CSVExportPanel.but.Selectnone = Odznacz wszystko SimExpPan.border.Fieldsep = Separator pl SimExpPan.lbl.Fieldsepstr = Ci\u0105g znakw - separator pl: SimExpPan.lbl.longA1 = Ci\u0105g znakw stosowany do oddzielania pl w eksportowanym pliku.
@@ -433,11 +433,11 @@ update.dlg.latestVersion = Korzystasz z najnowszej wersji OpenRocket: %s. SimExpPan.Fileexists.desc1 = Plik \" SimExpPan.Fileexists.desc2 = \" istnieje. Nadpisa\u0107? SimExpPan.Fileexists.title = Plik ju\u017C istnieje - SimExpPan.ExportingVar.desc1 = Eksportuj\u0119 1 zmienn\u0105 z - SimExpPan.ExportingVar.desc2 = Eksportuj\u0119 - SimExpPan.ExportingVar.desc3 = zmienne z - SimExpPan.Col.Variable = Zmienna - SimExpPan.Col.Unit = Jednostka + CSVExportPanel.ExportingVar.desc1 = Eksportuj\u0119 1 zmienn\u0105 z + CSVExportPanel.ExportingVar.desc2 = Eksportuj\u0119 + CSVExportPanel.ExportingVar.desc3 = zmienne z + CSVExportPanel.lbl.Variable = Zmienna + CSVExportPanel.lbl.Unit = Jednostka CsvOptionPanel.separator.space = SPACJA diff --git a/core/src/main/resources/l10n/messages_pt.properties b/core/src/main/resources/l10n/messages_pt.properties index 3565592c8..697c732d3 100644 --- a/core/src/main/resources/l10n/messages_pt.properties +++ b/core/src/main/resources/l10n/messages_pt.properties @@ -944,11 +944,11 @@ ShockCordCfg.lbl.plus = mais ShockCordCfg.tab.General = Geral ShockCordCfg.tab.ttip.General = Propriedades gerais -SimExpPan.Col.Unit = Unidade -SimExpPan.Col.Variable = Vari\u00e1vel -SimExpPan.ExportingVar.desc1 = Exportando uma vari\u00e1vel de -SimExpPan.ExportingVar.desc2 = Exportando -SimExpPan.ExportingVar.desc3 = vari\u00e1veis fora de +CSVExportPanel.lbl.Unit = Unidade +CSVExportPanel.lbl.Variable = Vari\u00e1vel +CSVExportPanel.ExportingVar.desc1 = Exportando uma vari\u00e1vel de +CSVExportPanel.ExportingVar.desc2 = Exportando +CSVExportPanel.ExportingVar.desc3 = vari\u00e1veis fora de SimExpPan.Fileexists.desc1 = Arquivo " SimExpPan.Fileexists.desc2 = " existe. Sobreescrever? SimExpPan.Fileexists.title = Arquivo existe @@ -956,8 +956,8 @@ SimExpPan.border.Comments = Coment\u00e1rios SimExpPan.border.Fieldsep = Separador de campo SimExpPan.border.Vartoexport = Vari\u00e1veis para exportar SimExpPan.but.Exporttofile = Exportar para arquivo... -SimExpPan.but.Selectall = Selecionar todos -SimExpPan.but.Selectnone = Limpar sele\u00e7\u00e3o +CSVExportPanel.but.Selectall = Selecionar todos +CSVExportPanel.but.Selectnone = Limpar sele\u00e7\u00e3o SimExpPan.checkbox.Incflightevents = Incluir eventos de voo SimExpPan.checkbox.Includefielddesc = Incluir descri\u00e7\u00f5es de campo SimExpPan.checkbox.Includesimudesc = Incluir a descri\u00e7\u00e3o de simula\u00e7\u00e3o diff --git a/core/src/main/resources/l10n/messages_ru.properties b/core/src/main/resources/l10n/messages_ru.properties index e8d90d919..6bd2670b3 100644 --- a/core/src/main/resources/l10n/messages_ru.properties +++ b/core/src/main/resources/l10n/messages_ru.properties @@ -555,8 +555,8 @@ SimulationModifierTree.OptimizationParameters = \u041E\u043F\u0442\u0438\u043C\u ! SimulationExportPanel SimExpPan.border.Vartoexport = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u043C\u044B\u0435 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0435 SimExpPan.border.Stage = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0442\u0443\u043F\u0435\u043D\u044C -SimExpPan.but.Selectall = \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435 -SimExpPan.but.Selectnone = \u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440 +CSVExportPanel.but.Selectall = \u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0432\u0441\u0435 +CSVExportPanel.but.Selectnone = \u041E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u0432\u044B\u0431\u043E\u0440 SimExpPan.border.FormatSettings = \u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440\u044B \u0444\u043E\u0440\u043C\u0430\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F SimExpPan.lbl.Fieldsepstr = \u0420\u0430\u0437\u0434\u0435\u043B\u0438\u0442\u0435\u043B\u044C \u043F\u043E\u043B\u0435\u0439: SimExpPan.lbl.longA1 = \u0421\u0442\u0440\u043E\u043A\u0430, \u0438\u0441\u043F\u043E\u043B\u044C\u0437\u0443\u0435\u043C\u0430\u044F \u0434\u043B\u044F \u0440\u0430\u0437\u0434\u0435\u043B\u0435\u043D\u0438\u044F \u043F\u043E\u043B\u0435\u0439 \u0432 \u044D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u043C\u043E\u043C \u0444\u0430\u0439\u043B\u0435.
@@ -577,11 +577,11 @@ SimExpPan.lbl.ttip.Commentchar = \u0421\u0438\u043C\u0432\u043E\u043B(\u044B), \ SimExpPan.Fileexists.desc1 = \u0424\u0430\u0439\u043B \" SimExpPan.Fileexists.desc2 = \" \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442. \u041F\u0435\u0440\u0435\u0437\u0430\u043F\u0438\u0441\u0430\u0442\u044C? SimExpPan.Fileexists.title = \u0424\u0430\u0439\u043B \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 -SimExpPan.ExportingVar.desc1 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u043E\u0434\u043D\u0430 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0438\u0437 -SimExpPan.ExportingVar.desc2 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F -SimExpPan.ExportingVar.desc3 = \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u0438\u0437 -SimExpPan.Col.Variable = \u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F -SimExpPan.Col.Unit = \u0415\u0434\u0438\u043D\u0438\u0446\u0430 \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044F +CSVExportPanel.ExportingVar.desc1 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F \u043E\u0434\u043D\u0430 \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F \u0438\u0437 +CSVExportPanel.ExportingVar.desc2 = \u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u0443\u0435\u0442\u0441\u044F +CSVExportPanel.ExportingVar.desc3 = \u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u0438\u0437 +CSVExportPanel.lbl.Variable = \u041F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u0430\u044F +CSVExportPanel.lbl.Unit = \u0415\u0434\u0438\u043D\u0438\u0446\u0430 \u0438\u0437\u043C\u0435\u0440\u0435\u043D\u0438\u044F CsvOptionPanel.separator.space = \u041F\u0440\u043E\u0431\u0435\u043B diff --git a/core/src/main/resources/l10n/messages_uk_UA.properties b/core/src/main/resources/l10n/messages_uk_UA.properties index ffc2ae8a4..e3a6b2714 100644 --- a/core/src/main/resources/l10n/messages_uk_UA.properties +++ b/core/src/main/resources/l10n/messages_uk_UA.properties @@ -682,8 +682,8 @@ SimulationStepper.error.totalMassZero = \u0417\u0430\u0433\u0430\u043b\u044c\u04 ! SimulationExportPanel SimExpPan.border.Vartoexport = \u0417\u043c\u0456\u043d\u043d\u0456 \u0434\u043b\u044f \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 SimExpPan.border.Stage = \u0415\u0442\u0430\u043f \u0434\u043b\u044f \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u0443 -SimExpPan.but.Selectall = \u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0432\u0441\u0435 -SimExpPan.but.Selectnone = \u0412\u0456\u0434\u043c\u0456\u043d\u0438\u0442\u0438 \u0432\u0441\u0435 +CSVExportPanel.but.Selectall = \u0412\u0438\u0431\u0440\u0430\u0442\u0438 \u0432\u0441\u0435 +CSVExportPanel.but.Selectnone = \u0412\u0456\u0434\u043c\u0456\u043d\u0438\u0442\u0438 \u0432\u0441\u0435 SimExpPan.border.FormatSettings = \u041d\u0430\u043b\u0430\u0448\u0442\u0443\u0432\u0430\u043d\u043d\u044f \u0444\u043e\u0440\u043c\u0430\u0442\u0443 SimExpPan.lbl.Fieldsepstr = \u0420\u044f\u0434\u043e\u043a \u0440\u043e\u0437\u0434\u0456\u043b\u044c\u043d\u0438\u043a\u0430 \u043f\u043e\u043b\u0456\u0432: SimExpPan.lbl.longA1 = \u0420\u044f\u0434\u043e\u043a, \u044f\u043a\u0438\u0439 \u0432\u0438\u043a\u043e\u0440\u0438\u0441\u0442\u043e\u0432\u0443\u0454\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u0440\u043e\u0437\u0434\u0456\u043b\u0435\u043d\u043d\u044f \u043f\u043e\u043b\u0456\u0432 \u0443 \u0435\u043a\u0441\u043f\u043e\u0440\u0442\u043e\u0432\u0430\u043d\u043e\u043c\u0443 \u0444\u0430\u0439\u043b\u0456.
@@ -704,11 +704,11 @@ SimExpPan.lbl.ttip.Commentchar = \u0421\u0438\u043c\u0432\u043e\u043b(\u0438), \ SimExpPan.Fileexists.desc1 = \u0424\u0430\u0439\u043b \" SimExpPan.Fileexists.desc2 = \" \u0456\u0441\u043d\u0443\u0454. \u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0438\u0441\u0430\u0442\u0438? SimExpPan.Fileexists.title = \u0424\u0430\u0439\u043b \u0456\u0441\u043d\u0443\u0454 -SimExpPan.ExportingVar.desc1 = \u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0437\u043c\u0456\u043d\u043d\u043e\u0457 1 \u0437 -SimExpPan.ExportingVar.desc2 = \u0415\u043a\u0441\u043f\u043e\u0440\u0442 -SimExpPan.ExportingVar.desc3 = \u0437\u043c\u0456\u043d\u043d\u0438\u0445 \u0437 -SimExpPan.Col.Variable = \u0417\u043c\u0456\u043d\u043d\u0430 -SimExpPan.Col.Unit = \u041e\u0434\u0438\u043d\u0438\u0446\u044f +CSVExportPanel.ExportingVar.desc1 = \u0415\u043a\u0441\u043f\u043e\u0440\u0442 \u0437\u043c\u0456\u043d\u043d\u043e\u0457 1 \u0437 +CSVExportPanel.ExportingVar.desc2 = \u0415\u043a\u0441\u043f\u043e\u0440\u0442 +CSVExportPanel.ExportingVar.desc3 = \u0437\u043c\u0456\u043d\u043d\u0438\u0445 \u0437 +CSVExportPanel.lbl.Variable = \u0417\u043c\u0456\u043d\u043d\u0430 +CSVExportPanel.lbl.Unit = \u041e\u0434\u0438\u043d\u0438\u0446\u044f CsvOptionPanel.separator.space = \u041f\u0420\u041e\u0411\u0406\u041b CsvOptionPanel.separator.tab = \u0412\u041a\u041b\u0410\u0414\u041a\u0410 diff --git a/core/src/main/resources/l10n/messages_zh_CN.properties b/core/src/main/resources/l10n/messages_zh_CN.properties index e373aa934..49efc0121 100644 --- a/core/src/main/resources/l10n/messages_zh_CN.properties +++ b/core/src/main/resources/l10n/messages_zh_CN.properties @@ -1026,11 +1026,11 @@ ShockCordCfg.lbl.plus = \u52A0 ShockCordCfg.tab.General = \u5E38\u89C4 ShockCordCfg.tab.ttip.General = \u5E38\u89C4\u5C5E\u6027 -SimExpPan.Col.Unit = \u5355\u4F4D -SimExpPan.Col.Variable = \u53D8\u91CF -SimExpPan.ExportingVar.desc1 = \u8F93\u51FA1\u4E2A\u53D8\u91CF\uFF0C\u5171\u8BA1 -SimExpPan.ExportingVar.desc2 = \u8F93\u51FA -SimExpPan.ExportingVar.desc3 = \u4E2A\u53D8\u91CF, \u5171\u8BA1 +CSVExportPanel.lbl.Unit = \u5355\u4F4D +CSVExportPanel.lbl.Variable = \u53D8\u91CF +CSVExportPanel.ExportingVar.desc1 = \u8F93\u51FA1\u4E2A\u53D8\u91CF\uFF0C\u5171\u8BA1 +CSVExportPanel.ExportingVar.desc2 = \u8F93\u51FA +CSVExportPanel.ExportingVar.desc3 = \u4E2A\u53D8\u91CF, \u5171\u8BA1 SimExpPan.Fileexists.desc1 = \u6587\u4EF6 " SimExpPan.Fileexists.desc2 = " \u5DF2\u5B58\u5728. \u8986\u76D6? SimExpPan.Fileexists.title = \u6587\u4EF6\u5DF2\u5B58\u5728 @@ -1038,8 +1038,8 @@ SimExpPan.border.Comments = \u6CE8\u91CA SimExpPan.border.Fieldsep = \u6570\u636E\u5206\u9694\u7B26 SimExpPan.border.Stage = \u5BFC\u51FA\u706B\u7BAD\u7EA7 SimExpPan.border.Vartoexport = \u5BFC\u51FA\u53D8\u91CF -SimExpPan.but.Selectall = \u5168\u9009 -SimExpPan.but.Selectnone = \u53D6\u6D88\u5168\u9009 +CSVExportPanel.but.Selectall = \u5168\u9009 +CSVExportPanel.but.Selectnone = \u53D6\u6D88\u5168\u9009 SimExpPan.checkbox.Incflightevents = \u98DE\u884C\u4E8B\u4EF6 SimExpPan.checkbox.Includefielddesc = \u6570\u636E\u57DF\u63CF\u8FF0 SimExpPan.checkbox.Includesimudesc = \u4EFF\u771F\u63CF\u8FF0 diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java index 2944fabd8..f05e7d661 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAExportPanel.java @@ -2,7 +2,6 @@ package info.openrocket.swing.gui.dialogs.componentanalysis; import info.openrocket.core.componentanalysis.CADataBranch; import info.openrocket.core.componentanalysis.CADataType; -import info.openrocket.core.l10n.Translator; import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; @@ -13,75 +12,122 @@ import info.openrocket.swing.gui.util.SaveCSVWorker; import info.openrocket.swing.gui.util.SwingPreferences; import info.openrocket.swing.gui.widgets.CSVExportPanel; import info.openrocket.swing.gui.widgets.SaveFileChooser; +import net.miginfocom.swing.MigLayout; -import javax.swing.AbstractCellEditor; -import javax.swing.BorderFactory; -import javax.swing.DefaultCellEditor; -import javax.swing.JCheckBox; -import javax.swing.JComponent; +import javax.swing.JButton; import javax.swing.JFileChooser; -import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.JTable; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.SwingUtilities; -import javax.swing.table.DefaultTableCellRenderer; -import javax.swing.table.TableCellEditor; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import java.awt.Color; +import javax.swing.UIManager; import java.awt.Component; -import java.awt.Graphics; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; +import java.awt.Dimension; +import java.awt.Font; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; public class CAExportPanel extends CSVExportPanel { private static final long serialVersionUID = 4423905472892675964L; - private static final Translator trans = Application.getTranslator(); - private static final int FIXED_COMPONENT_COLUMN_WIDTH = 500; + private static final SwingPreferences prefs = (SwingPreferences) Application.getPreferences(); + + private final ComponentAnalysisPlotExportPanel parent; + private final Map> selectedComponents = new HashMap<>(); + private List selectedComponentsLabels; + private List selectedComponentsScrollPanes; private static final int OPTION_COMPONENT_ANALYSIS_COMMENTS = 0; private static final int OPTION_FIELD_DESCRIPTIONS = 1; - private final List> selectedComponents; - private final ComponentAnalysisPlotExportPanel parent; - - private CAExportPanel(ComponentAnalysisPlotExportPanel parent, CADataType[] types, - boolean[] selected, CsvOptionPanel csvOptions, Component... extraComponents) { - super(types, selected, csvOptions, true, extraComponents); + public CAExportPanel(ComponentAnalysisPlotExportPanel parent, CADataType[] types, boolean[] selected) { + super(types, selected, + new CsvOptionPanel(CAExportPanel.class, true, + trans.get("CAExportPanel.checkbox.Includecadesc"), + trans.get("CAExportPanel.checkbox.ttip.Includecadesc"), + trans.get("SimExpPan.checkbox.Includefielddesc"), + trans.get("SimExpPan.checkbox.ttip.Includefielddesc")), + false); this.parent = parent; - selectedComponents = new ArrayList<>(types.length); - Map componentSelectedMap; - List components; - for (CADataType type : types) { - components = parent.getComponentsForType(type); - componentSelectedMap = new HashMap<>(components.size()); - for (int i = 0; i < components.size(); i++) { - // Select the first component by default - componentSelectedMap.put(components.get(i), i == 0); - } - selectedComponents.add(componentSelectedMap); + // Initialize selected components map + for (int i = 0; i < types.length; i++) { + updateSelectedComponents(types[i], new ArrayList<>(), parent.getComponentsForType(types[i]), + selectedComponentsLabels.get(i), selectedComponentsScrollPanes.get(i)); } + } - // Set row heights dynamically - for (int row = 0; row < table.getRowCount(); row++) { - int numComponents = ((Map) table.getValueAt(row, 3)).size(); - double correctNumComponents = Math.ceil(numComponents / 3.0); // 3 components per row - double height = Math.round(correctNumComponents * 25) + 10; // 25 pixels per component + 10 pixel margin - int rowHeight = Math.max(table.getRowHeight(), (int) height); - table.setRowHeight(row, rowHeight); - } + @Override + protected Component createExtraComponent(CADataType type, int index) { + JPanel panel = new JPanel(new MigLayout("ins 0, fill", "[grow]", "[][]")); + + // Label for displaying selected components + JTextArea selectedComponentsLabel = new JTextArea(); + selectedComponentsLabel.setEditable(false); + selectedComponentsLabel.setWrapStyleWord(true); + selectedComponentsLabel.setLineWrap(true); + selectedComponentsLabel.setOpaque(false); + selectedComponentsLabel.setFont(UIManager.getFont("Label.font").deriveFont(Font.PLAIN, prefs.getUIFontSize() - 1)); + + JScrollPane scrollPane = new JScrollPane(selectedComponentsLabel); + scrollPane.setPreferredSize(new Dimension(200, 50)); // Adjust as needed + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + + panel.add(scrollPane, "growx, wrap"); + + selectedComponentsLabels = selectedComponentsLabels != null ? selectedComponentsLabels : new ArrayList<>(); + selectedComponentsScrollPanes = selectedComponentsScrollPanes != null ? selectedComponentsScrollPanes : new ArrayList<>(); + selectedComponentsLabels.add(selectedComponentsLabel); + selectedComponentsScrollPanes.add(scrollPane); + + // Select components button + JButton selectComponentsBtn = new JButton(trans.get("CAExportPanel.btn.SelectComponents")); + panel.add(selectComponentsBtn, "growx, wrap"); + + selectComponentsBtn.addActionListener(e -> { + List availableComponents = parent.getComponentsForType(type); + ComponentSelectionDialog dialog = new ComponentSelectionDialog( + SwingUtilities.getWindowAncestor(this), + parent.getDocument(), + availableComponents, + selectedComponents.get(type) + ); + List newSelectedComponents = dialog.showDialog(); + updateSelectedComponents(type, newSelectedComponents, availableComponents, selectedComponentsLabel, scrollPane); + }); + + return panel; + } + + @Override + protected String getExtraColumnLabelKey() { + return "CAExportPanel.Col.Components"; + } + + private void updateSelectedComponents(CADataType type, List components, + List componentsForType, JTextArea label, + JScrollPane scrollPane) { + components = (components != null && !components.isEmpty()) ? components : componentsForType.subList(0, 1); + List selectedComponentsList = this.selectedComponents.computeIfAbsent(type, k -> new ArrayList<>()); + selectedComponentsList.clear(); + selectedComponentsList.addAll(components); + + updateSelectedComponentsLabel(label, scrollPane, components); + } + + private void updateSelectedComponentsLabel(JTextArea label, JScrollPane scrollPane, List components) { + String componentNames = components.stream() + .map(RocketComponent::getName) + .reduce((a, b) -> a + ", " + b) + .orElse(""); + label.setText(componentNames); + label.setToolTipText(componentNames); // Add tooltip in case the text is too long + scrollPane.revalidate(); + scrollPane.repaint(); } public static CAExportPanel create(ComponentAnalysisPlotExportPanel parent, CADataType[] types) { @@ -90,39 +136,7 @@ public class CAExportPanel extends CSVExportPanel { selected[i] = ((SwingPreferences) Application.getPreferences()).isComponentAnalysisDataTypeExportSelected(types[i]); } - CsvOptionPanel csvOptions = new CsvOptionPanel(CAExportPanel.class, false, - trans.get("CAExportPanel.checkbox.Includecadesc"), - trans.get("CAExportPanel.checkbox.ttip.Includecadesc"), - trans.get("SimExpPan.checkbox.Includefielddesc"), - trans.get("SimExpPan.checkbox.ttip.Includefielddesc")); - - return new CAExportPanel(parent, types, selected, csvOptions); - } - - protected void initializeTable(CADataType[] types) { - super.initializeTable(types); - - // Set custom renderers for each column - TableColumn firstColumn = table.getColumnModel().getColumn(0); - firstColumn.setCellRenderer(new CheckBoxRenderer()); - firstColumn.setCellEditor(new CheckBoxEditor()); - table.getColumnModel().getColumn(1).setCellRenderer(new LeftAlignedRenderer()); - table.getColumnModel().getColumn(2).setCellRenderer(new LeftAlignedRenderer()); - - ComponentCheckBoxPanel.ComponentSelectionListener listener = (newStates) -> { - int row = table.getSelectedRow(); - if (row != -1) { - selectedComponents.set(row, newStates); - } - }; - - TableColumn componentColumn = table.getColumnModel().getColumn(3); - componentColumn.setCellRenderer(new ComponentCheckBoxRenderer(listener)); - componentColumn.setCellEditor(new ComponentCheckBoxEditor(listener)); - componentColumn.setPreferredWidth(FIXED_COMPONENT_COLUMN_WIDTH); - - // Set specific client properties for FlatLaf - table.setShowHorizontalLines(true); + return new CAExportPanel(parent, types, selected); } @Override @@ -133,7 +147,7 @@ public class CAExportPanel extends CSVExportPanel { List typesWithNoComponents = new ArrayList<>(); for (int i = 0; i < selected.length; i++) { if (selected[i]) { - boolean hasSelectedComponent = selectedComponents.get(i).values().stream().anyMatch(v -> v); + boolean hasSelectedComponent = !selectedComponents.get(i).isEmpty(); if (!hasSelectedComponent) { typesWithNoComponents.add(types[i]); } @@ -188,12 +202,9 @@ public class CAExportPanel extends CSVExportPanel { csvOptions.storePreferences(); // Store preferences and export - int n = 0; ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory()); for (int i = 0; i < selected.length; i++) { ((SwingPreferences) Application.getPreferences()).setComponentAnalysisExportSelected(types[i], selected[i]); - if (selected[i]) - n++; } List fieldTypes = new ArrayList<>(); @@ -203,12 +214,7 @@ public class CAExportPanel extends CSVExportPanel { // Iterate through the table to get selected items for (int i = 0; i < selected.length; i++) { if (selected[i]) { - List selectedComponentsList = new ArrayList<>(); - for (Map.Entry entry : selectedComponents.get(i).entrySet()) { - if (entry.getValue()) { - selectedComponentsList.add(entry.getKey()); - } - } + List selectedComponentsList = new ArrayList<>(selectedComponents.get(i)); if (!selectedComponentsList.isEmpty()) { fieldTypes.add(types[i]); fieldUnits.add(units[i]); @@ -232,315 +238,4 @@ public class CAExportPanel extends CSVExportPanel { return true; } - - @Override - protected CSVExportPanel.SelectionTableModel createTableModel() { - return new CASelectionTableModel(); - } - - protected class CASelectionTableModel extends SelectionTableModel { - private static final int COMPONENTS = 3; - - @Override - public int getColumnCount() { - return 4; - } - - @Override - public String getColumnName(int column) { - //// Components - if (column == COMPONENTS) { - return trans.get("CAExportPanel.Col.Components"); - } - return super.getColumnName(column); - } - - @Override - public Class getColumnClass(int column) { - //// Components - if (column == COMPONENTS) { - return Map.class; - } - return super.getColumnClass(column); - } - - @Override - public Object getValueAt(int row, int column) { - if (column == COMPONENTS) { - return selectedComponents.get(row); - } - return super.getValueAt(row, column); - } - - @Override - public void setValueAt(Object value, int row, int column) { - if (column == COMPONENTS) { - selectedComponents.set(row, (Map) value); - fireTableCellUpdated(row, column); - } else { - super.setValueAt(value, row, column); - } - } - - @Override - public boolean isCellEditable(int row, int column) { - if (column == COMPONENTS) { - return true; - } - return super.isCellEditable(row, column); - } - } - - private static class CheckBoxRenderer extends JCheckBox implements TableCellRenderer { - public CheckBoxRenderer() { - setHorizontalAlignment(JLabel.CENTER); - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - setSelected((Boolean) value); - setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); - setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); // Add top padding - setVerticalAlignment(JLabel.TOP); - return this; - } - } - - private static class CheckBoxEditor extends DefaultCellEditor { - public CheckBoxEditor() { - super(new JCheckBox()); - ((JCheckBox)getComponent()).setHorizontalAlignment(JLabel.CENTER); - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - Component c = super.getTableCellEditorComponent(table, value, isSelected, row, column); - if (c instanceof JCheckBox) { - JCheckBox cb = (JCheckBox) c; - cb.setVerticalAlignment(JLabel.TOP); - cb.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); - } - return c; - } - } - - private static class LeftAlignedRenderer extends DefaultTableCellRenderer { - public LeftAlignedRenderer() { - setHorizontalAlignment(JLabel.LEFT); - setVerticalAlignment(JLabel.TOP); - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); - c.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - c.setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); - ((JComponent) c).setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 0)); // Add top and left padding - return c; - } - } - - private static class ComponentCheckBoxRenderer implements TableCellRenderer { - private ComponentCheckBoxPanel panel; - private final ComponentCheckBoxPanel.ComponentSelectionListener listener; - - public ComponentCheckBoxRenderer(ComponentCheckBoxPanel.ComponentSelectionListener listener) { - this.listener = listener; - } - - @Override - public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { - if (!(value instanceof Map)) { - JLabel errorLabel = new JLabel("Invalid data"); - errorLabel.setForeground(Color.RED); - return errorLabel; - } - - @SuppressWarnings("unchecked") - Map componentMap = (Map) value; - - if (panel == null) { - panel = new ComponentCheckBoxPanel(componentMap); - panel.setComponentSelectionListener(listener); - } else { - panel.updateComponentStates(componentMap); - } - - panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - panel.setGridColor(table.getGridColor()); - panel.setSelected(isSelected); - - return panel; - } - } - - private static class ComponentCheckBoxPanel extends JPanel { - private final Map checkBoxMap = new HashMap<>(); - private final AtomicBoolean updatingState = new AtomicBoolean(false); - private Color gridColor = Color.GRAY; - private boolean isSelected = false; - - public ComponentCheckBoxPanel(Map componentMap) { - setLayout(new GridBagLayout()); - GridBagConstraints gbc = new GridBagConstraints(); - gbc.anchor = GridBagConstraints.NORTHWEST; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.insets = new Insets(2, 2, 2, 2); - - createCheckBoxes(componentMap, gbc); - - // Add an empty component to push everything to the top-left - gbc.gridx = 0; - gbc.gridy = (componentMap.size() + 2) / 3 + 1; - gbc.weighty = 1.0; - gbc.fill = GridBagConstraints.BOTH; - add(new JPanel(), gbc); - - // Ensure at least one checkbox is selected - if (checkBoxMap.values().stream().noneMatch(JCheckBox::isSelected) && !checkBoxMap.isEmpty()) { - checkBoxMap.values().iterator().next().setSelected(true); - } - } - - private void createCheckBoxes(Map componentMap, GridBagConstraints gbc) { - int row = 0; - int col = 0; - for (Map.Entry entry : componentMap.entrySet()) { - RocketComponent component = entry.getKey(); - Boolean isSelected = entry.getValue(); - - // Skip null components - if (component == null) { - continue; - } - - String componentName = component.getName(); - // Use a default name if getName() returns null - if (componentName == null) { - componentName = "Unnamed Component"; - } - - JCheckBox checkBox = new JCheckBox(componentName, isSelected != null && isSelected); - checkBox.setOpaque(false); - checkBox.setMargin(new Insets(0, 0, 0, 0)); - checkBox.addItemListener(checkBoxListener); - checkBoxMap.put(component, checkBox); - - gbc.gridx = col; - gbc.gridy = row; - add(checkBox, gbc); - - col++; - if (col > 2) { - col = 0; - row++; - } - } - } - - public void updateComponentStates(Map newStates) { - updatingState.set(true); - try { - for (Map.Entry entry : newStates.entrySet()) { - JCheckBox checkBox = checkBoxMap.get(entry.getKey()); - if (checkBox != null) { - checkBox.setSelected(entry.getValue()); - } - } - } finally { - updatingState.set(false); - } - } - - public Map getComponentStates() { - return checkBoxMap.entrySet().stream() - .collect(HashMap::new, - (m, e) -> m.put(e.getKey(), e.getValue().isSelected()), - HashMap::putAll); - } - - public void setGridColor(Color color) { - this.gridColor = color; - } - - public void setSelected(boolean selected) { - this.isSelected = selected; - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - // Draw the bottom border - g.setColor(gridColor); - g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1); - - // If selected, draw a slight highlight - if (isSelected) { - g.setColor(new Color(0, 0, 255, 30)); // Semi-transparent blue - g.fillRect(0, 0, getWidth(), getHeight() - 1); - } - } - - public interface ComponentSelectionListener { - void onComponentSelectionChanged(Map newStates); - } - - private ComponentSelectionListener listener; - - public void setComponentSelectionListener(ComponentSelectionListener listener) { - this.listener = listener; - } - - private final ItemListener checkBoxListener = e -> { - if (updatingState.get()) return; - - // Remove the check for deselection and forced selection - if (listener != null) { - listener.onComponentSelectionChanged(getComponentStates()); - } - - // Notify the table that the value has changed - firePropertyChange("value", null, getComponentStates()); - }; - } - - private static class ComponentCheckBoxEditor extends AbstractCellEditor implements TableCellEditor { - private ComponentCheckBoxPanel panel; - private final ComponentCheckBoxPanel.ComponentSelectionListener listener; - - public ComponentCheckBoxEditor(ComponentCheckBoxPanel.ComponentSelectionListener listener) { - this.listener = listener; - } - - @Override - public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { - if (!(value instanceof Map)) { - // Return a default component if value is not a Map - JLabel errorLabel = new JLabel("Invalid data"); - errorLabel.setForeground(Color.RED); - return errorLabel; - } - - @SuppressWarnings("unchecked") - Map componentMap = (Map) value; - - if (panel == null) { - panel = new ComponentCheckBoxPanel(componentMap); - panel.setComponentSelectionListener(listener); - } else { - panel.updateComponentStates(componentMap); - } - - panel.setBackground(isSelected ? table.getSelectionBackground() : table.getBackground()); - return panel; - } - - @Override - public Object getCellEditorValue() { - return panel.getComponentStates(); - } - } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java index fe39f937f..ba98485dd 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlot.java @@ -2,11 +2,12 @@ package info.openrocket.swing.gui.dialogs.componentanalysis; import info.openrocket.core.componentanalysis.CADataBranch; import info.openrocket.core.componentanalysis.CADataType; +import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.unit.Unit; import info.openrocket.swing.gui.plot.Plot; import org.jfree.data.xy.XYSeries; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; public class CAPlot extends Plot { @@ -18,12 +19,27 @@ public class CAPlot extends Plot @Override protected List createSeriesForType(int dataIndex, int startIndex, CADataType type, Unit unit, CADataBranch branch, int branchIdx, String branchName, String baseName) { - // Default implementation for regular DataBranch - MetadataXYSeries series = new MetadataXYSeries(startIndex, false, true, branchIdx, unit.getUnit(), - branchName, baseName); + // Get the component info + List components = filledConfig.getComponents(dataIndex); + List componentNames = filledConfig.getComponentNames(dataIndex); - // Get the component name - String componentName = filledConfig.getComponentName(dataIndex); + // Create the series for each component + List allSeries = new ArrayList<>(); + for (int i = 0; i < components.size(); i++) { + XYSeries series = createSingleSeries(startIndex*1000 + i, type, unit, branch, branchIdx, branchName, baseName, + components.get(i), componentNames.get(i)); + allSeries.add(series); + } + + return allSeries; + } + + private XYSeries createSingleSeries(int key, CADataType type, Unit unit, + CADataBranch branch, int branchIdx, String branchName, String baseName, + RocketComponent component, String componentName) { + // Default implementation for regular DataBranch + MetadataXYSeries series = new MetadataXYSeries(key, false, true, branchIdx, unit.getUnit(), + branchName, baseName); // Create a new description that includes the component name String newBaseName = baseName; @@ -34,7 +50,7 @@ public class CAPlot extends Plot series.updateDescription(); List plotx = branch.get(filledConfig.getDomainAxisType()); - List ploty = branch.get(type, filledConfig.getComponent(dataIndex)); + List ploty = branch.get(type, component); int pointCount = plotx.size(); for (int j = 0; j < pointCount; j++) { @@ -43,6 +59,6 @@ public class CAPlot extends Plot series.add(x, y); } - return Collections.singletonList(series); + return series; } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotConfiguration.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotConfiguration.java index 122c883e9..bc8244b0d 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotConfiguration.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotConfiguration.java @@ -7,21 +7,21 @@ import info.openrocket.core.l10n.Translator; import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; -import info.openrocket.core.util.ArrayList; import info.openrocket.swing.gui.plot.Axis; import info.openrocket.swing.gui.plot.PlotConfiguration; +import java.util.ArrayList; import java.util.List; public class CAPlotConfiguration extends PlotConfiguration { private static final Translator trans = Application.getTranslator(); - private ArrayList plotDataComponents = new ArrayList<>(); + private List> plotDataComponents = new ArrayList<>(); public static final CAPlotConfiguration[] DEFAULT_CONFIGURATIONS; static { - ArrayList configs = new ArrayList<>(); + List configs = new ArrayList<>(); CAPlotConfiguration config; //// Total CD vs Mach @@ -53,17 +53,21 @@ public class CAPlotConfiguration extends PlotConfiguration getComponents(int index) { return plotDataComponents.get(index); } - public void setPlotDataComponent(int index, RocketComponent component) { - plotDataComponents.set(index, component); + public void setPlotDataComponents(int index, List components) { + plotDataComponents.set(index, components); } - public String getComponentName(int dataIndex) { - RocketComponent component = getComponent(dataIndex); - return component != null ? component.getName() : ""; + public List getComponentNames(int dataIndex) { + List components = getComponents(dataIndex); + List names = new ArrayList<>(components.size()); + for (RocketComponent c : components) { + names.add(c != null ? c.getName() : ""); + } + return names; } @Override @@ -78,8 +82,12 @@ public class CAPlotConfiguration extends PlotConfiguration(plotDataComponents.size()); + for (List components : plotDataComponents) { + clone.plotDataComponents.add(components != null ? new ArrayList<>(components) : null); + } return clone; } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotPanel.java index 70b2c1028..4219ec953 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotPanel.java @@ -4,7 +4,6 @@ import info.openrocket.core.componentanalysis.CADataBranch; import info.openrocket.core.componentanalysis.CADataType; import info.openrocket.core.componentanalysis.CADataTypeGroup; import info.openrocket.core.componentanalysis.CADomainDataType; -import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.unit.Unit; import info.openrocket.swing.gui.plot.PlotPanel; @@ -38,8 +37,7 @@ public class CAPlotPanel extends PlotPanel { if (modifying > 0) return; - RocketComponent component = selector.getSelectedComponent(); - configuration.setPlotDataComponent(idx, component); + configuration.setPlotDataComponents(idx, selector.getSelectedComponents()); }); } @Override protected CAPlotTypeSelector createSelector(int i, CADataType type, Unit unit, int axis) { - return new CAPlotTypeSelector(parent, i, type, unit, axis, List.of(typesY), - parent.getComponentsForType(type), configuration, configuration.getComponent(i)); + return new CAPlotTypeSelector(parent, parent.getDocument(), i, type, unit, axis, List.of(typesY), + parent.getComponentsForType(type), configuration, configuration.getComponents(i)); } public void setXAxis(CADomainDataType type) { diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java index f860750e9..61f5d89fd 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/CAPlotTypeSelector.java @@ -2,39 +2,79 @@ package info.openrocket.swing.gui.dialogs.componentanalysis; import info.openrocket.core.componentanalysis.CADataType; import info.openrocket.core.componentanalysis.CADataTypeGroup; +import info.openrocket.core.document.OpenRocketDocument; import info.openrocket.core.rocketcomponent.RocketComponent; +import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; import info.openrocket.core.util.StringUtils; import info.openrocket.swing.gui.plot.PlotTypeSelector; +import info.openrocket.swing.gui.util.SwingPreferences; +import net.miginfocom.swing.MigLayout; -import javax.swing.JComboBox; -import javax.swing.JLabel; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.UIManager; +import java.awt.Dimension; +import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.awt.event.ItemListener; +import java.util.ArrayList; import java.util.List; public class CAPlotTypeSelector extends PlotTypeSelector { - private final JComboBox componentSelector; + private static final SwingPreferences prefs = (SwingPreferences) Application.getPreferences(); - public CAPlotTypeSelector(final ComponentAnalysisPlotExportPanel parent, int plotIndex, - CADataType type, Unit unit, int position, List availableTypes, + private final JTextArea selectedComponentsLabel; + private final JScrollPane selectedComponentsScrollPane; + private final List selectedComponents = new ArrayList<>(); + private List componentsForType; + private final List componentSelectionListeners = new ArrayList<>(); + + public CAPlotTypeSelector(final ComponentAnalysisPlotExportPanel parent, OpenRocketDocument document, + int plotIndex, CADataType type, Unit unit, int position, List availableTypes, List componentsForType, CAPlotConfiguration configuration, - RocketComponent selectedComponent) { + List initialSelectedComponents) { super(plotIndex, type, unit, position, availableTypes, false); - if (componentsForType.isEmpty()) { - throw new IllegalArgumentException("No components for type " + type); - } + // Selected components label + selectedComponentsLabel = new JTextArea(); + selectedComponentsLabel.setEditable(false); + selectedComponentsLabel.setWrapStyleWord(true); + selectedComponentsLabel.setLineWrap(true); + selectedComponentsLabel.setOpaque(false); + selectedComponentsLabel.setFont(UIManager.getFont("Label.font").deriveFont(Font.PLAIN, prefs.getUIFontSize() - 1)); + + selectedComponentsScrollPane = new JScrollPane(selectedComponentsLabel); + selectedComponentsScrollPane.setPreferredSize(new Dimension(200, 60)); // Adjust as needed + selectedComponentsScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + + // Update the data + setComponentsForType(type, componentsForType); + updateSelectedComponents(initialSelectedComponents, componentsForType, configuration, plotIndex); // Component selector - selectedComponent = selectedComponent != null ? selectedComponent : componentsForType.get(0); - this.add(new JLabel(trans.get("CAPlotTypeSelector.lbl.component"))); - componentSelector = new JComboBox<>(componentsForType.toArray(new RocketComponent[0])); - componentSelector.setSelectedItem(selectedComponent); - configuration.setPlotDataComponent(plotIndex, selectedComponent); - this.add(componentSelector, "growx, gapright para"); + final JPanel componentSelectorPanel = new JPanel(new MigLayout("ins 0")); + JButton selectComponentButton = new JButton(trans.get("CAPlotTypeSelector.btn.SelectComponents")); + selectComponentButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + ComponentSelectionDialog dialog = new ComponentSelectionDialog(parent.getParentWindow(), document, + CAPlotTypeSelector.this.componentsForType, CAPlotTypeSelector.this.selectedComponents); + List selectedComponents = dialog.showDialog(); + if (selectedComponents != null) { + updateSelectedComponents(selectedComponents, CAPlotTypeSelector.this.componentsForType, + configuration, plotIndex); + } + } + }); + componentSelectorPanel.add(selectComponentButton, "spanx, wrap"); + componentSelectorPanel.add(selectedComponentsScrollPane, "wrap"); + this.add(componentSelectorPanel, "growx, gapleft para, gapright para"); + + // Remove button addRemoveButton(); typeSelector.addActionListener(new ActionListener() { @@ -42,32 +82,65 @@ public class CAPlotTypeSelector extends PlotTypeSelector componentsForType = parent.getComponentsForType(type); - componentSelector.removeAllItems(); - for (RocketComponent component : componentsForType) { - componentSelector.addItem(component); - } - componentSelector.setSelectedIndex(0); - configuration.setPlotDataComponent(plotIndex, (RocketComponent) componentSelector.getSelectedItem()); + setComponentsForType(type, componentsForType); + updateSelectedComponents(null, componentsForType, configuration, plotIndex); } }); } - public CAPlotTypeSelector(final ComponentAnalysisPlotExportPanel parent, int plotIndex, - CADataType type, Unit unit, int position, List availableTypes, - List componentsForType, CAPlotConfiguration configuration) { - this(parent, plotIndex, type, unit, position, availableTypes, componentsForType, configuration, null); + private void setComponentsForType(CADataType type, List componentsForType) { + if (componentsForType.isEmpty()) { + throw new IllegalArgumentException("No components for type " + type); + } + this.componentsForType = componentsForType; } - public void addComponentSelectionListener(ItemListener listener) { - componentSelector.addItemListener(listener); + private void updateSelectedComponents(List components, List componentsForType, + CAPlotConfiguration configuration, int plotIndex) { + components = (components != null && !components.isEmpty()) ? components : componentsForType.subList(0, 1); + this.selectedComponents.clear(); + this.selectedComponents.addAll(components); + + updateSelectedComponentsLabel(this.selectedComponents); + configuration.setPlotDataComponents(plotIndex, this.selectedComponents); + notifyComponentSelectionListeners(); } - public RocketComponent getSelectedComponent() { - return (RocketComponent) componentSelector.getSelectedItem(); + private void updateSelectedComponentsLabel(List components) { + List names = new ArrayList<>(components.size()); + for (RocketComponent c : components) { + names.add(c.getName()); + } + selectedComponentsLabel.setText(StringUtils.join(", ", names)); + selectedComponentsLabel.setToolTipText(StringUtils.join(", ", names)); // Add tooltip in case the text is too long + selectedComponentsScrollPane.revalidate(); + selectedComponentsScrollPane.repaint(); + } + + public void addComponentSelectionListener(ComponentSelectionListener listener) { + componentSelectionListeners.add(listener); + } + + public void removeComponentSelectionListener(ComponentSelectionListener listener) { + componentSelectionListeners.remove(listener); + } + + private void notifyComponentSelectionListeners() { + for (ComponentSelectionListener listener : this.componentSelectionListeners) { + listener.componentsSelected(this.selectedComponents); + } + } + + public List getSelectedComponents() { + return this.selectedComponents; } @Override protected String getDisplayString(CADataType item) { return StringUtils.removeHTMLTags(item.getName()); } + + public interface ComponentSelectionListener { + void componentsSelected(List selectedComponents); + } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisDialog.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisDialog.java index 27cc6b0da..abfefc70a 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisDialog.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisDialog.java @@ -1,5 +1,6 @@ package info.openrocket.swing.gui.dialogs.componentanalysis; +import info.openrocket.core.document.OpenRocketDocument; import info.openrocket.core.l10n.Translator; import info.openrocket.core.startup.Application; @@ -28,7 +29,7 @@ public class ComponentAnalysisDialog extends JDialog { private JButton okButton; - public ComponentAnalysisDialog(RocketPanel rocketPanel) { + public ComponentAnalysisDialog(OpenRocketDocument document, RocketPanel rocketPanel) { //// Component analysis super(SwingUtilities.getWindowAncestor(rocketPanel), trans.get("ComponentAnalysisDialog.componentanalysis")); @@ -42,8 +43,8 @@ public class ComponentAnalysisDialog extends JDialog { tabbedPane.addTab(trans.get("ComponentAnalysisDialog.tab.General"), generalTab); // Plot export tab - ComponentAnalysisPlotExportPanel plotExportTab = new ComponentAnalysisPlotExportPanel(this, generalTab.getParameters(), - generalTab.getAerodynamicCalculator(), generalTab.getRocket()); + ComponentAnalysisPlotExportPanel plotExportTab = new ComponentAnalysisPlotExportPanel(this, document, + generalTab.getParameters(), generalTab.getAerodynamicCalculator(), generalTab.getRocket()); tabbedPane.addTab(trans.get("ComponentAnalysisDialog.tab.PlotExport"), plotExportTab); panel.add(tabbedPane, "span, pushy, grow, wrap"); @@ -94,10 +95,10 @@ public class ComponentAnalysisDialog extends JDialog { ///////// Singleton implementation - public static void showDialog(RocketPanel rocketpanel) { + public static void showDialog(OpenRocketDocument document, RocketPanel rocketpanel) { if (singletonDialog != null) singletonDialog.dispose(); - singletonDialog = new ComponentAnalysisDialog(rocketpanel); + singletonDialog = new ComponentAnalysisDialog(document, rocketpanel); singletonDialog.setVisible(true); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java index cfbc31f9f..6176b1f80 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentAnalysisPlotExportPanel.java @@ -6,6 +6,7 @@ import info.openrocket.core.componentanalysis.CADataType; import info.openrocket.core.componentanalysis.CADomainDataType; import info.openrocket.core.componentanalysis.CAParameterSweep; import info.openrocket.core.componentanalysis.CAParameters; +import info.openrocket.core.document.OpenRocketDocument; import info.openrocket.core.l10n.Translator; import info.openrocket.core.rocketcomponent.Rocket; import info.openrocket.core.rocketcomponent.RocketComponent; @@ -41,6 +42,7 @@ public class ComponentAnalysisPlotExportPanel extends JPanel implements PlotPane private static final Logger log = LoggerFactory.getLogger(ComponentAnalysisPlotExportPanel.class); private final Window parent; + private final OpenRocketDocument document; private final JTabbedPane tabbedPane; JComboBox parameterSelector; private JButton okButton; @@ -62,11 +64,13 @@ public class ComponentAnalysisPlotExportPanel extends JPanel implements PlotPane private final Map> componentCache; private boolean isCacheValid; - public ComponentAnalysisPlotExportPanel(ComponentAnalysisDialog parent, CAParameters parameters, - AerodynamicCalculator aerodynamicCalculator, Rocket rocket) { + public ComponentAnalysisPlotExportPanel(ComponentAnalysisDialog parent, OpenRocketDocument document, + CAParameters parameters, AerodynamicCalculator aerodynamicCalculator, + Rocket rocket) { super(new MigLayout("fill, height 700px!", "[]", "[grow]")); this.parent = parent; + this.document = document; this.parameters = parameters; this.parameterSweep = new CAParameterSweep(parameters, aerodynamicCalculator, rocket); this.componentCache = new HashMap<>(); @@ -216,6 +220,10 @@ public class ComponentAnalysisPlotExportPanel extends JPanel implements PlotPane }); } + public OpenRocketDocument getDocument() { + return document; + } + public CADomainDataType getSelectedParameter() { return (CADomainDataType) parameterSelector.getSelectedItem(); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentSelectionDialog.java b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentSelectionDialog.java new file mode 100644 index 000000000..7e1c498c2 --- /dev/null +++ b/swing/src/main/java/info/openrocket/swing/gui/dialogs/componentanalysis/ComponentSelectionDialog.java @@ -0,0 +1,85 @@ +package info.openrocket.swing.gui.dialogs.componentanalysis; + +import info.openrocket.core.document.OpenRocketDocument; +import info.openrocket.core.l10n.Translator; +import info.openrocket.core.rocketcomponent.RocketComponent; +import info.openrocket.core.startup.Application; +import info.openrocket.swing.gui.main.componenttree.SelectableComponentTree; + +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.tree.TreePath; +import java.awt.BorderLayout; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +public class ComponentSelectionDialog extends JDialog { + private static final Translator trans = Application.getTranslator(); + + private final SelectableComponentTree componentTree; + private List selectedComponents; + + public ComponentSelectionDialog(Window owner, OpenRocketDocument document, List enabledComponents, + List initialSelection) { + super(owner, "Select Components", ModalityType.APPLICATION_MODAL); + + // Component tree + componentTree = new SelectableComponentTree(document, enabledComponents, initialSelection); + JScrollPane scrollPane = new JScrollPane(componentTree); + + // Confirm button + JButton confirmButton = new JButton(trans.get("ComponentSelectionDialog.btn.ConfirmSelection")); + confirmButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateSelectedComponents(); + dispose(); + } + }); + + // Cancel button + JButton cancelButton = new JButton(trans.get("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectedComponents = null; + dispose(); + } + }); + + JPanel buttonPanel = new JPanel(); + buttonPanel.add(confirmButton); + buttonPanel.add(cancelButton); + + setLayout(new BorderLayout()); + add(scrollPane, BorderLayout.CENTER); + add(buttonPanel, BorderLayout.SOUTH); + + setSize(400, 500); + setLocationRelativeTo(owner); + setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + } + + private void updateSelectedComponents() { + TreePath[] selectedPaths = componentTree.getSelectionPaths(); + selectedComponents = new ArrayList<>(); + if (selectedPaths != null) { + for (TreePath path : selectedPaths) { + Object component = path.getLastPathComponent(); + if (component instanceof RocketComponent) { + selectedComponents.add((RocketComponent) component); + } + } + } + } + + public List showDialog() { + setVisible(true); + return selectedComponents; + } +} diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java b/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java index cd6c6f02c..a56ff61bb 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java +++ b/swing/src/main/java/info/openrocket/swing/gui/main/BasicFrame.java @@ -663,7 +663,7 @@ public class BasicFrame extends JFrame { @Override public void actionPerformed(ActionEvent e) { log.info(Markers.USER_MARKER, "Component analysis selected"); - ComponentAnalysisDialog.showDialog(rocketpanel); + ComponentAnalysisDialog.showDialog(document, rocketpanel); } }); toolsMenu.add(item); diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java index 5deaacca2..86063e642 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java +++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ComponentTreeRenderer.java @@ -35,10 +35,6 @@ public class ComponentTreeRenderer extends DefaultTreeCellRenderer { private static final Translator trans = Application.getTranslator(); - private static Color textSelectionBackgroundColor; - private static Color textSelectionForegroundColor; - private static Color componentTreeBackgroundColor; - private static Color componentTreeForegroundColor; private static Color visibilityHiddenForegroundColor; private static Icon massOverrideSubcomponentIcon; private static Icon massOverrideIcon; diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTree.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTree.java new file mode 100644 index 000000000..8a3fa30f7 --- /dev/null +++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTree.java @@ -0,0 +1,42 @@ +package info.openrocket.swing.gui.main.componenttree; + +import info.openrocket.core.document.OpenRocketDocument; +import info.openrocket.core.rocketcomponent.RocketComponent; + +import javax.swing.tree.TreePath; +import java.util.List; +import java.util.Set; +import java.util.HashSet; + +public class SelectableComponentTree extends ComponentTree { + + public SelectableComponentTree(OpenRocketDocument document, List enabledComponents, + List initialSelection) { + super(document); + Set enabledComponents1 = new HashSet<>(enabledComponents); + + // Use a custom cell renderer that considers the enabled state + setCellRenderer(new SelectableComponentTreeRenderer(enabledComponents1)); + + // Use a custom selection model that allows toggling of selection + ToggleTreeSelectionModel selectionModel = new ToggleTreeSelectionModel(); + setSelectionModel(selectionModel); + + // Disable component selection for disabled components + for (RocketComponent component : document.getRocket()) { + if (!enabledComponents.contains(component)) { + TreePath path = ComponentTreeModel.makeTreePath(component); + selectionModel.addDisabledPath(path); + } + } + + // Apply initial selection + selectionModel.setSelectionPath(null); + if (initialSelection != null && !initialSelection.isEmpty()) { + for (RocketComponent component : initialSelection) { + TreePath path = ComponentTreeModel.makeTreePath(component); + selectionModel.addSelectionPath(path); + } + } + } +} \ No newline at end of file diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTreeRenderer.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTreeRenderer.java new file mode 100644 index 000000000..0539419ce --- /dev/null +++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/SelectableComponentTreeRenderer.java @@ -0,0 +1,78 @@ +package info.openrocket.swing.gui.main.componenttree; + +import info.openrocket.core.rocketcomponent.RocketComponent; +import info.openrocket.swing.gui.theme.UITheme; +import info.openrocket.swing.gui.util.GUIUtil; +import info.openrocket.swing.gui.util.Icons; + +import javax.swing.Icon; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTree; +import javax.swing.UIManager; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.util.Set; + +public class SelectableComponentTreeRenderer extends ComponentTreeRenderer { + private final Set enabledComponents; + + private static Color disabledTextColor; + private static Font regularFont; + private static Font italicFont; + + static { + initColors(); + initFonts(); + } + + public SelectableComponentTreeRenderer(Set enabledComponents) { + this.enabledComponents = enabledComponents; + } + + private static void initColors() { + updateColors(); + UITheme.Theme.addUIThemeChangeListener(SelectableComponentTreeRenderer::updateColors); + } + + private static void initFonts() { + regularFont = UIManager.getFont("Tree.font"); + italicFont = regularFont.deriveFont(Font.ITALIC); + } + + private static void updateColors() { + disabledTextColor = GUIUtil.getUITheme().getDisabledTextColor(); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, + boolean sel, boolean expanded, boolean leaf, int row, + boolean hasFocus) { + Component rendererComponent = super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + + if (value instanceof RocketComponent component) { + boolean isEnabled = enabledComponents.contains(component); + rendererComponent.setEnabled(isEnabled); + + if (rendererComponent instanceof JPanel panel) { + for (Component c : panel.getComponents()) { + if (c instanceof JLabel label) { + if (!isEnabled) { + label.setForeground(disabledTextColor); + label.setFont(italicFont); + Icon icon = label.getIcon(); + if (icon != null) { + label.setIcon(Icons.createDisabledIcon(icon)); + } + } else { + label.setFont(regularFont); + } + } + } + } + } + + return rendererComponent; + } +} \ No newline at end of file diff --git a/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ToggleTreeSelectionModel.java b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ToggleTreeSelectionModel.java new file mode 100644 index 000000000..578b9e040 --- /dev/null +++ b/swing/src/main/java/info/openrocket/swing/gui/main/componenttree/ToggleTreeSelectionModel.java @@ -0,0 +1,53 @@ +package info.openrocket.swing.gui.main.componenttree; + +import javax.swing.tree.DefaultTreeSelectionModel; +import javax.swing.tree.TreePath; +import java.util.HashSet; +import java.util.Set; + +public class ToggleTreeSelectionModel extends DefaultTreeSelectionModel { + private final Set disabledPaths; + + public ToggleTreeSelectionModel() { + setSelectionMode(DISCONTIGUOUS_TREE_SELECTION); + disabledPaths = new HashSet<>(); + } + + @Override + public void setSelectionPath(TreePath path) { + if (isPathEnabled(path)) { + if (isPathSelected(path)) { + removeSelectionPath(path); + } else { + addSelectionPath(path); + } + } + } + + @Override + public void addSelectionPath(TreePath path) { + if (isPathEnabled(path)) { + super.addSelectionPath(path); + } + } + + @Override + public void removeSelectionPath(TreePath path) { + if (isPathEnabled(path)) { + super.removeSelectionPath(path); + } + } + + public void addDisabledPath(TreePath path) { + disabledPaths.add(path); + removeSelectionPath(path); + } + + public void removeDisabledPath(TreePath path) { + disabledPaths.remove(path); + } + + public boolean isPathEnabled(TreePath path) { + return !disabledPaths.contains(path); + } +} \ No newline at end of file diff --git a/swing/src/main/java/info/openrocket/swing/gui/plot/PlotTypeSelector.java b/swing/src/main/java/info/openrocket/swing/gui/plot/PlotTypeSelector.java index 8cbd7b471..aa48aab98 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/plot/PlotTypeSelector.java +++ b/swing/src/main/java/info/openrocket/swing/gui/plot/PlotTypeSelector.java @@ -52,19 +52,19 @@ public class PlotTypeSelector & UnitValue, G extends Grou } }; typeSelector.setSelectedItem(type); - this.add(typeSelector, "gapright para"); + this.add(typeSelector, "gapright para, top"); - this.add(new JLabel("Unit:")); + this.add(new JLabel("Unit:"), "top"); unitSelector = new UnitSelector(type.getUnitGroup()); if (unit != null) { unitSelector.setSelectedUnit(unit); } - this.add(unitSelector, "width 40lp, gapright para"); + this.add(unitSelector, "width 40lp, gapright para, top"); - this.add(new JLabel("Axis:")); + this.add(new JLabel("Axis:"), "top"); axisSelector = new JComboBox<>(POSITIONS); axisSelector.setSelectedIndex(position + 1); - this.add(axisSelector); + this.add(axisSelector, "top"); removeButton = new JButton(Icons.EDIT_DELETE); removeButton.setToolTipText("Remove this plot"); @@ -79,7 +79,7 @@ public class PlotTypeSelector & UnitValue, G extends Grou } protected void addRemoveButton() { - this.add(removeButton, "gapright 0"); + this.add(removeButton, "gapright 0, top"); } public int getIndex() { diff --git a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java index 2e4d79258..c8fe035ba 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/simulation/SimulationExportPanel.java @@ -44,7 +44,7 @@ public class SimulationExportPanel extends CSVExportPanel { private SimulationExportPanel(Simulation simulation, FlightDataBranch branch, FlightDataType[] types, boolean[] selected, CsvOptionPanel csvOptions, Component... extraComponents) { - super(types, selected, csvOptions, extraComponents); + super(types, selected, csvOptions, false, extraComponents); this.simulation = simulation; this.branch = branch; } @@ -165,4 +165,9 @@ public class SimulationExportPanel extends CSVExportPanel { return true; } + + @Override + protected String getDisplayName(FlightDataType type) { + return type.getName(); + } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java b/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java index db7536c4e..a64bd1865 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java +++ b/swing/src/main/java/info/openrocket/swing/gui/theme/UITheme.java @@ -1041,7 +1041,7 @@ public class UITheme { @Override public Color getDisabledTextColor() { - return new Color(128, 128, 128); + return new Color(128, 128, 128, 223); } diff --git a/swing/src/main/java/info/openrocket/swing/gui/util/Icons.java b/swing/src/main/java/info/openrocket/swing/gui/util/Icons.java index f81a6e5a8..47be8b867 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/util/Icons.java +++ b/swing/src/main/java/info/openrocket/swing/gui/util/Icons.java @@ -7,11 +7,13 @@ import info.openrocket.core.startup.Application; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.swing.GrayFilter; import javax.swing.Icon; import javax.swing.ImageIcon; import java.awt.Component; import java.awt.Graphics; import java.awt.Image; +import java.awt.image.BufferedImage; import java.net.URL; import java.util.Collections; import java.util.HashMap; @@ -183,4 +185,13 @@ public class Icons { } }; } + + public static Icon createDisabledIcon(Icon icon) { + Image image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics g = image.getGraphics(); + icon.paintIcon(null, g, 0, 0); + g.dispose(); + + return new ImageIcon(GrayFilter.createDisabledImage(((ImageIcon) icon).getImage())); + } } diff --git a/swing/src/main/java/info/openrocket/swing/gui/widgets/CSVExportPanel.java b/swing/src/main/java/info/openrocket/swing/gui/widgets/CSVExportPanel.java index 36fb68927..24ef699f9 100644 --- a/swing/src/main/java/info/openrocket/swing/gui/widgets/CSVExportPanel.java +++ b/swing/src/main/java/info/openrocket/swing/gui/widgets/CSVExportPanel.java @@ -3,334 +3,240 @@ package info.openrocket.swing.gui.widgets; import info.openrocket.core.l10n.Translator; import info.openrocket.core.startup.Application; import info.openrocket.core.unit.Unit; -import info.openrocket.core.unit.UnitGroup; import info.openrocket.core.util.UnitValue; import info.openrocket.swing.gui.components.CsvOptionPanel; -import info.openrocket.swing.gui.components.UnitCellEditor; +import info.openrocket.swing.gui.components.UnitSelector; +import info.openrocket.swing.gui.theme.UITheme; import info.openrocket.swing.gui.util.GUIUtil; import net.miginfocom.swing.MigLayout; import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.table.AbstractTableModel; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; +import javax.swing.UIManager; +import java.awt.Color; import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Arrays; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.util.ArrayList; +import java.util.List; public class CSVExportPanel extends JPanel { - private static final Translator trans = Application.getTranslator(); + private static final long serialVersionUID = 1L; + protected static final Translator trans = Application.getTranslator(); protected static final String SPACE = "SPACE"; protected static final String TAB = "TAB"; + private static Color ALTERNATE_ROW_COLOR; - protected final JTable table; - protected final SelectionTableModel tableModel; - private final JLabel selectedCountLabel; - - protected final boolean[] selected; - protected final T[] types; - protected final Unit[] units; - + protected final List dataTypeRows = new ArrayList<>(); protected final CsvOptionPanel csvOptions; + protected final JLabel selectedCountLabel; - public CSVExportPanel(T[] types, boolean[] selected, CsvOptionPanel csvOptions, boolean separateRowForTable, Component... extraComponents) { - super(new MigLayout("fill, flowy")); + protected final T[] types; + protected boolean[] selected; + protected Unit[] units; + + static { + initColors(); + } + + public CSVExportPanel(T[] types, boolean[] selected, CsvOptionPanel csvOptions, boolean separateRowForOptions, + Component... extraComponents) { + super(new MigLayout("fill, wrap 2", "[grow,fill][]", "[grow,fill][]")); this.types = types; this.selected = selected; this.csvOptions = csvOptions; - this.units = new Unit[types.length]; + for (int i = 0; i < types.length; i++) { units[i] = types[i].getUnitGroup().getDefaultUnit(); } - //// Create the panel - JPanel panel; - JButton button; + JPanel exportPanel = new JPanel(new MigLayout("ins 0, fill")); - // Set up the variable selection table - tableModel = createTableModel(); - table = new JTable(tableModel); - initializeTable(types); + boolean addExtras = createExtraComponent(types[0], 0) != null; + JPanel contentPanel = new JPanel(new GridBagLayout()); + contentPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(0, 0, 0, 0); // No padding + c.fill = GridBagConstraints.BOTH; - // Add table - panel = new JPanel(new MigLayout("fill")); - panel.setBorder(BorderFactory.createTitledBorder(trans.get("SimExpPan.border.Vartoexport"))); + // Header + c.gridy = 0; + c.weightx = 0; + addHeaderRowLabel(contentPanel, "CSVExportPanel.lbl.Export", c, 0); + c.weightx = 1; + addHeaderRowLabel(contentPanel, "CSVExportPanel.lbl.Variable", c, 1); + c.weightx = 0; + addHeaderRowLabel(contentPanel, "CSVExportPanel.lbl.Unit", c, 2); + if (addExtras) { + c.weightx = 1; + addHeaderRowLabel(contentPanel, getExtraColumnLabelKey(), c, 3); + } - panel.add(new JScrollPane(table), "wmin 300lp, width 300lp, pushy, grow 100, wrap"); + // Data rows + for (int i = 0; i < types.length; i++) { + DataTypeRow row = new DataTypeRow(types[i], selected[i], units[i], i); + dataTypeRows.add(row); + + Color bgColor = i % 2 == 0 ? UIManager.getColor("Table.background") : ALTERNATE_ROW_COLOR; + + c.gridy = i + 1; + + addDataRowWidget(contentPanel, row.exportCheckBox, c, bgColor, 0); + addDataRowWidget(contentPanel, row.nameLabel, c, bgColor, 1); + addDataRowWidget(contentPanel, row.unitSelector, c, bgColor, 2); + if (addExtras) { + addDataRowWidget(contentPanel, createExtraComponent(types[i], i), c, bgColor, 3); + } + } + + JScrollPane scrollPane = new JScrollPane(contentPanel); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setPreferredSize(new Dimension(300, 400)); + exportPanel.add(scrollPane, "grow, wrap"); // Select all/none buttons - button = new JButton(trans.get("SimExpPan.but.Selectall")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tableModel.selectAll(); - } - }); - panel.add(button, "split 2, growx 1, sizegroup selectbutton"); + JPanel buttonPanel = new JPanel(new MigLayout("insets 0")); + JButton selectAllButton = new JButton(trans.get("CSVExportPanel.but.Selectall")); + selectAllButton.addActionListener(e -> selectAll()); + buttonPanel.add(selectAllButton, "split 2, growx"); - button = new JButton(trans.get("SimExpPan.but.Selectnone")); - button.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - tableModel.selectNone(); - } - }); - panel.add(button, "growx 1, sizegroup selectbutton, wrap"); + JButton selectNoneButton = new JButton(trans.get("CSVExportPanel.but.Selectnone")); + selectNoneButton.addActionListener(e -> selectNone()); + buttonPanel.add(selectNoneButton, "growx"); + exportPanel.add(buttonPanel, "growx, wrap"); - // Exporting xx variables out of yy + // Selected count label selectedCountLabel = new JLabel(); updateSelectedCount(); - panel.add(selectedCountLabel); + exportPanel.add(selectedCountLabel, "growx"); + add(exportPanel, "grow, gapright para" + (separateRowForOptions ? ", spanx, wrap" : "")); - if (separateRowForTable) { - this.add(panel, "spanx, grow 100, wrap"); - } else { - this.add(panel, "grow 100, wrap"); - } - - // Add CSV options - if (separateRowForTable) { - this.add(csvOptions, "grow 1"); - } else { - this.add(csvOptions, "spany, split, growx 1"); - } - - //// Add extra widgets - if (extraComponents != null) { - for (Component c : extraComponents) { - if (separateRowForTable) { - this.add(c, "grow 1"); - } else { - this.add(c, "spany, split, growx 1"); - } + // CSV options and extra components + if (separateRowForOptions) { + add(csvOptions, "growx"); + for (Component comp : extraComponents) { + add(comp, "growx, top"); } - } - - // Space-filling panel - if (!separateRowForTable) { - panel = new JPanel(); - this.add(panel, "width 1, height 1, grow 1"); - } - } - - public CSVExportPanel(T[] types, boolean[] selected, CsvOptionPanel csvOptions, Component... extraComponents) { - this(types, selected, csvOptions, false, extraComponents); - } - - protected SelectionTableModel createTableModel() { - return new SelectionTableModel(); - } - - protected void initializeTable(T[] types) { - table.setDefaultRenderer(Object.class, - new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Object.class))); - table.setDefaultRenderer(Boolean.class, - new SelectionBackgroundCellRenderer(table.getDefaultRenderer(Boolean.class))); - table.setRowSelectionAllowed(false); - table.setColumnSelectionAllowed(false); - - table.setDefaultEditor(Unit.class, new UnitCellEditor() { - private static final long serialVersionUID = 1088570433902420935L; - - @Override - protected UnitGroup getUnitGroup(Unit value, int row, int column) { - return types[row].getUnitGroup(); + } else { + JPanel optionsPanel = new JPanel(new MigLayout("insets 0")); + optionsPanel.add(csvOptions, "growx"); + for (Component comp : extraComponents) { + optionsPanel.add(comp, "growx"); } - }); - - // Set column widths - TableColumnModel columnModel = table.getColumnModel(); - TableColumn col = columnModel.getColumn(0); - int w = table.getRowHeight(); - col.setMinWidth(w); - col.setPreferredWidth(w); - col.setMaxWidth(w); - - col = columnModel.getColumn(1); - col.setPreferredWidth(200); - - col = columnModel.getColumn(2); - col.setPreferredWidth(100); - - table.addMouseListener(new GUIUtil.BooleanTableClickListener(table)); + add(optionsPanel, "growx, top"); + } } - public boolean doExport() { - throw new RuntimeException("Not implemented"); + private static void initColors() { + updateColors(); + UITheme.Theme.addUIThemeChangeListener(CSVExportPanel::updateColors); } - private void updateSelectedCount() { + private static void updateColors() { + ALTERNATE_ROW_COLOR = GUIUtil.getUITheme().getRowBackgroundLighterColor(); + } + + private void addHeaderRowLabel(JPanel contentPanel, String lblKey, GridBagConstraints c, int x) { + c.gridx = x; + + JPanel panel = new JPanel(new MigLayout("fill, ins 4 5 4 5")); + panel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("Table.gridColor"))); + panel.add(new JLabel("" + trans.get(lblKey) + ""), "growx"); + contentPanel.add(panel, c); + } + + private void addDataRowWidget(JPanel contentPanel, Component widget, GridBagConstraints c, Color bgColor, int x) { + c.gridx = x; + + JPanel panel = new JPanel(new MigLayout("fill, ins 4 5 4 5")); + panel.setBackground(bgColor); + if (widget instanceof JPanel) { + widget.setBackground(bgColor); + } + panel.add(widget, "growx"); + contentPanel.add(panel, c); + } + + protected String getExtraColumnLabelKey() { + return "CSVExportPanel.lbl.Extra"; + } + + protected Component createExtraComponent(T type, int index) { + return null; + } + + protected class DataTypeRow { + final JCheckBox exportCheckBox; + final JLabel nameLabel; + final UnitSelector unitSelector; + final int index; + + DataTypeRow(T type, boolean isSelected, Unit unit, int index) { + this.index = index; + exportCheckBox = new JCheckBox(); + exportCheckBox.setSelected(isSelected); + exportCheckBox.addActionListener(e -> { + selected[index] = exportCheckBox.isSelected(); + updateSelectedCount(); + }); + + nameLabel = new JLabel(getDisplayName(type)); + + unitSelector = new UnitSelector(type.getUnitGroup()); + unitSelector.setSelectedUnit(unit); + unitSelector.addItemListener(e -> units[index] = unitSelector.getSelectedUnit()); + } + } + + protected String getDisplayName(T type) { + return type.toString(); + } + + protected void selectAll() { + for (DataTypeRow row : dataTypeRows) { + row.exportCheckBox.setSelected(true); + selected[row.index] = true; + } + updateSelectedCount(); + } + + protected void selectNone() { + for (DataTypeRow row : dataTypeRows) { + row.exportCheckBox.setSelected(false); + selected[row.index] = false; + } + updateSelectedCount(); + } + + protected void updateSelectedCount() { int total = selected.length; int n = 0; - String str; - for (boolean b : selected) { - if (b) - n++; + if (b) n++; } + String str; if (n == 1) { - //// Exporting 1 variable out of - str = trans.get("SimExpPan.ExportingVar.desc1") + " " + total + "."; + str = trans.get("CSVExportPanel.ExportingVar.desc1") + " " + total + "."; } else { - //// Exporting - //// variables out of - str = trans.get("SimExpPan.ExportingVar.desc2") + " " + n + " " + - trans.get("SimExpPan.ExportingVar.desc3") + " " + total + "."; + str = trans.get("CSVExportPanel.ExportingVar.desc2") + " " + n + " " + + trans.get("CSVExportPanel.ExportingVar.desc3") + " " + total + "."; } selectedCountLabel.setText(str); } - /** - * The table model for the variable selection. - */ - protected class SelectionTableModel extends AbstractTableModel { - private static final long serialVersionUID = 493067422917621072L; - protected static final int SELECTED = 0; - protected static final int NAME = 1; - protected static final int UNIT = 2; - - @Override - public int getColumnCount() { - return 3; - } - - @Override - public int getRowCount() { - return types.length; - } - - @Override - public String getColumnName(int column) { - return switch (column) { - case SELECTED -> ""; - case NAME -> - //// Variable - trans.get("SimExpPan.Col.Variable"); - case UNIT -> - //// Unit - trans.get("SimExpPan.Col.Unit"); - default -> throw new IndexOutOfBoundsException("column=" + column); - }; - } - - @Override - public Class getColumnClass(int column) { - return switch (column) { - case SELECTED -> Boolean.class; - case NAME -> new TypeToken(){}.getType().getClass(); - case UNIT -> Unit.class; - default -> throw new IndexOutOfBoundsException("column=" + column); - }; - } - - @Override - public Object getValueAt(int row, int column) { - return switch (column) { - case SELECTED -> selected[row]; - case NAME -> types[row]; - case UNIT -> units[row]; - default -> throw new IndexOutOfBoundsException("column=" + column); - }; - } - - @Override - public void setValueAt(Object value, int row, int column) { - switch (column) { - case SELECTED: - selected[row] = (Boolean) value; - this.fireTableRowsUpdated(row, row); - updateSelectedCount(); - break; - - case NAME: - break; - - case UNIT: - units[row] = (Unit) value; - break; - - default: - throw new IndexOutOfBoundsException("column=" + column); - } - } - - @Override - public boolean isCellEditable(int row, int column) { - return switch (column) { - case SELECTED -> true; - case NAME -> false; - case UNIT -> types[row].getUnitGroup().getUnitCount() > 1; - default -> throw new IndexOutOfBoundsException("column=" + column); - }; - } - - public void selectAll() { - Arrays.fill(selected, true); - updateSelectedCount(); - this.fireTableDataChanged(); - } - - public void selectNone() { - Arrays.fill(selected, false); - updateSelectedCount(); - this.fireTableDataChanged(); - } - + public boolean doExport() { + throw new UnsupportedOperationException("Export not implemented in base class"); } - - /** - * A table cell renderer that uses another renderer and sets the background and - * foreground of the returned component based on the selection of the variable. - */ - private class SelectionBackgroundCellRenderer implements TableCellRenderer { - private final TableCellRenderer renderer; - - public SelectionBackgroundCellRenderer(TableCellRenderer renderer) { - this.renderer = renderer; - } - - @Override - public Component getTableCellRendererComponent(JTable myTable, Object value, - boolean isSelected, boolean hasFocus, int row, int column) { - Component component = renderer.getTableCellRendererComponent(myTable, - value, isSelected, hasFocus, row, column); - - if (selected[row]) { - component.setBackground(myTable.getSelectionBackground()); - component.setForeground(myTable.getSelectionForeground()); - } else { - component.setBackground(myTable.getBackground()); - component.setForeground(myTable.getForeground()); - } - - return component; - } - } - - public abstract static class TypeToken { - private final Type type; - - protected TypeToken(){ - Type superClass = getClass().getGenericSuperclass(); - this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0]; - } - - public Type getType() { - return type; - } - } -} +} \ No newline at end of file