1 package net.sf.openrocket.gui.preset;
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
8 import java.io.FileReader;
9 import java.io.IOException;
10 import java.util.ArrayList;
11 import java.util.List;
13 import javax.swing.AbstractAction;
14 import javax.swing.Action;
15 import javax.swing.JButton;
16 import javax.swing.JDialog;
17 import javax.swing.JFileChooser;
18 import javax.swing.JFrame;
19 import javax.swing.JLabel;
20 import javax.swing.JMenu;
21 import javax.swing.JMenuBar;
22 import javax.swing.JMenuItem;
23 import javax.swing.JOptionPane;
24 import javax.swing.JPanel;
25 import javax.swing.JScrollPane;
26 import javax.swing.JSeparator;
27 import javax.swing.JTable;
28 import javax.swing.table.DefaultTableModel;
29 import javax.xml.bind.JAXBException;
31 import net.miginfocom.swing.MigLayout;
32 import net.sf.openrocket.gui.util.FileHelper;
33 import net.sf.openrocket.gui.util.Icons;
34 import net.sf.openrocket.gui.util.SwingPreferences;
35 import net.sf.openrocket.l10n.ResourceBundleTranslator;
36 import net.sf.openrocket.logging.LogHelper;
37 import net.sf.openrocket.material.Material;
38 import net.sf.openrocket.preset.ComponentPreset;
39 import net.sf.openrocket.preset.loader.MaterialHolder;
40 import net.sf.openrocket.preset.loader.RocksimComponentFileTranslator;
41 import net.sf.openrocket.preset.xml.OpenRocketComponentDTO;
42 import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
43 import net.sf.openrocket.startup.Application;
46 * A UI for editing component presets. Currently this is a standalone application - run the main within this class.
47 * TODO: Full I18n TODO: Save As .csv
49 public class ComponentPresetEditor extends JPanel implements PresetResultListener {
54 private static final LogHelper log = Application.getLogger();
57 * The I18N translator.
59 private static ResourceBundleTranslator trans = null;
62 * The table of presets.
67 * The table's data model.
69 private DataTableModel model;
72 * Flag that indicates if an existing Preset is currently being edited.
74 // private boolean editingSelected = false;
76 private final OpenedFileContext editContext = new OpenedFileContext();
79 trans = new ResourceBundleTranslator("l10n.messages");
80 net.sf.openrocket.startup.Application.setBaseTranslator(trans);
86 * @param frame the parent window
88 public ComponentPresetEditor(final JFrame frame) {
89 setLayout(new MigLayout("", "[82.00px, grow][168.00px, grow][84px, grow][117.00px, grow][][222px]",
90 "[346.00px, grow][29px]"));
92 model = new DataTableModel(new String[]{"Manufacturer", "Type", "Part No", "Description", ""});
94 table = new JTable(model);
95 table.getTableHeader().setFont(new JLabel().getFont());
96 //The action never gets called because the table MouseAdapter intercepts it first. Still need an empty
98 Action action = new AbstractAction() {
100 public void actionPerformed(final ActionEvent e) {
103 //Create a editor/renderer for the delete operation. Instantiation self-registers into the table.
104 new ButtonColumn(table, action, 4);
105 table.getColumnModel().getColumn(4).setMaxWidth(Icons.EDIT_DELETE.getIconWidth());
106 table.getColumnModel().getColumn(4).setMinWidth(Icons.EDIT_DELETE.getIconWidth());
108 JScrollPane scrollPane = new JScrollPane(table);
109 table.setFillsViewportHeight(true);
110 table.setAutoCreateRowSorter(true);
111 add(scrollPane, "cell 0 0 6 1,grow");
113 table.addMouseListener(new MouseAdapter() {
114 public void mouseClicked(MouseEvent e) {
115 JTable target = (JTable) e.getSource();
116 int selectedColumn = table.getColumnModel().getColumnIndexAtX( target.getSelectedColumn() );
117 int selectedRow = table.getRowSorter().convertRowIndexToModel( target.getSelectedRow() );
118 if (selectedColumn == 4) {
119 if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(ComponentPresetEditor.this,
120 "Do you want to delete this preset?",
121 "Confirm Delete", JOptionPane.YES_OPTION,
122 JOptionPane.QUESTION_MESSAGE)) {
123 model.removeRow(selectedRow);
127 if (e.getClickCount() == 2) {
128 editContext.setEditingSelected(true);
129 new PresetEditorDialog(ComponentPresetEditor.this,
130 (ComponentPreset) model.getAssociatedObject(selectedRow), editContext.getMaterialsLoaded()).setVisible(true);
137 JMenuBar menuBar = new JMenuBar();
138 frame.setJMenuBar(menuBar);
140 JMenu mnFile = new JMenu("File");
143 JMenuItem mntmOpen = new JMenuItem("Open...");
144 mnFile.add(mntmOpen);
145 mntmOpen.addActionListener(new ActionListener() {
147 public void actionPerformed(final ActionEvent e) {
148 if (model.getRowCount() > 0) {
150 * If the table model already contains presets, ask the user if they a) want to discard those
151 * presets, b) save them before reading in another component file, or c) merge the read component file
152 * with the current contents of the table model.
154 Object[] options = {"Save",
158 int n = JOptionPane.showOptionDialog(frame,
159 "The editor contains existing component presets. What would you like to do with them?",
160 "Existing Component Presets",
161 JOptionPane.YES_NO_CANCEL_OPTION,
162 JOptionPane.QUESTION_MESSAGE,
166 if (n == 0) { //Save. Then remove existing rows and open.
167 if (saveAndHandleError()) {
168 model.removeAllRows();
170 else { //Save failed; bail out.
174 else if (n == 2) { //Discard and open
175 model.removeAllRows();
177 else if (n == 3) { //Cancel. Bail out.
186 JSeparator separator = new JSeparator();
187 mnFile.add(separator);
189 JMenuItem mntmSave = new JMenuItem("Save As...");
190 mnFile.add(mntmSave);
191 mntmSave.addActionListener(new ActionListener() {
193 public void actionPerformed(final ActionEvent e) {
194 saveAndHandleError();
198 JSeparator separator_1 = new JSeparator();
199 mnFile.add(separator_1);
201 JMenuItem mntmExit = new JMenuItem("Exit");
202 mnFile.add(mntmExit);
203 mntmExit.addActionListener(new ActionListener() {
204 public void actionPerformed(ActionEvent arg0) {
210 JButton addBtn = new JButton("Add");
211 addBtn.addMouseListener(new MouseAdapter() {
213 public void mouseClicked(MouseEvent arg0) {
214 editContext.setEditingSelected(false);
215 new PresetEditorDialog(ComponentPresetEditor.this).setVisible(true);
218 add(addBtn, "cell 0 1,alignx left,aligny top");
222 * Callback method from the PresetEditorDialog to notify this class when a preset has been saved. The 'save' is
223 * really just a call back here so the preset can be added to the master table. It's not to be confused with the
226 * @param preset the new or modified preset
229 public void notifyResult(final ComponentPreset preset) {
230 if (preset != null) {
231 DataTableModel model = (DataTableModel) table.getModel();
232 //Is this a new preset?
233 String description = preset.has(ComponentPreset.DESCRIPTION) ? preset.get(ComponentPreset.DESCRIPTION) :
235 if (!editContext.isEditingSelected()|| table.getSelectedRow() == -1) {
236 model.addRow(new Object[]{preset.getManufacturer().getDisplayName(), preset.getType().name(),
237 preset.getPartNo(), description, Icons.EDIT_DELETE}, preset);
240 //This is a modified preset; update all of the columns and the stored associated instance.
241 int row = table.getSelectedRow();
242 model.setValueAt(preset.getManufacturer().getDisplayName(), row, 0);
243 model.setValueAt(preset.getType().name(), row, 1);
244 model.setValueAt(preset.getPartNo(), row, 2);
245 model.setValueAt(description, row, 3);
246 model.associated.set(row, preset);
249 editContext.setEditingSelected(false);
253 * Launch the test main.
255 public static void main(String[] args) {
257 Application.setPreferences(new SwingPreferences());
258 JFrame dialog = new JFrame();
259 dialog.getContentPane().add(new ComponentPresetEditor(dialog));
260 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
262 dialog.setVisible(true);
264 catch (Exception e) {
270 * A table model that adds associated objects to each row, allowing for easy retrieval.
272 class DataTableModel extends DefaultTableModel {
274 private List<Object> associated = new ArrayList<Object>();
277 * Constructs a <code>DefaultTableModel</code> with as many columns as there are elements in
278 * <code>columnNames</code> and <code>rowCount</code> of <code>null</code> object values. Each column's name
279 * will be taken from the <code>columnNames</code> array.
281 * @param columnNames <code>array</code> containing the names of the new columns; if this is <code>null</code>
282 * then the model has no columns
284 * @see #setDataVector
287 DataTableModel(final Object[] columnNames) {
288 super(columnNames, 0);
291 public void addRow(Object[] data, Object associatedData) {
293 associated.add(getRowCount() - 1, associatedData);
296 public void removeAllRows() {
297 for (int x = getRowCount(); x > 0; x--) {
298 super.removeRow(x - 1);
303 public void removeRow(int row) {
304 super.removeRow(row);
305 associated.remove(row);
308 public Object getAssociatedObject(int row) {
309 return associated.get(row);
312 public boolean isCellEditable(int rowIndex, int mColIndex) {
318 * Open the component file. Present a chooser for the user to navigate to the file.
320 * @return true if the file was successfully opened; Note: side effect, is that the ComponentPresets read from the
321 * file are written to the table model.
323 private boolean openComponentFile() {
324 final JFileChooser chooser = new JFileChooser();
325 chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
326 chooser.addChoosableFileFilter(FileHelper.CSV_FILE_FILTER);
327 chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
328 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
329 if (editContext.getLastDirectory() != null) {
330 chooser.setCurrentDirectory(editContext.getLastDirectory());
333 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
336 int option = chooser.showOpenDialog(ComponentPresetEditor.this);
337 if (option != JFileChooser.APPROVE_OPTION) {
338 editContext.setOpenedFile(null);
339 log.user("User decided not to open, option=" + option);
343 File file = chooser.getSelectedFile();
346 log.user("User did not select a file");
350 editContext.setLastDirectory(file.getParentFile());
351 editContext.setMaterialsLoaded(null);
352 List<ComponentPreset> presets = null;
354 if (file.getName().toLowerCase().endsWith(".orc")) {
355 OpenRocketComponentDTO fileContents = new OpenRocketComponentSaver().unmarshalFromOpenRocketComponent(new FileReader(file));
356 editContext.setMaterialsLoaded( new MaterialHolder(fileContents.asMaterialList()) );
357 presets = fileContents.asComponentPresets();
360 if (file.getName().toLowerCase().endsWith(".csv")) {
361 file = file.getParentFile();
363 presets = new ArrayList<ComponentPreset>();
364 MaterialHolder materialHolder = RocksimComponentFileTranslator.loadAll(presets, file);
365 editContext.setMaterialsLoaded(materialHolder);
367 if (presets != null) {
368 for (ComponentPreset next : presets) {
371 editContext.setOpenedFile(file);
374 catch (Exception e) {
375 JOptionPane.showMessageDialog(ComponentPresetEditor.this, "Unable to open OpenRocket component file: " +
376 file.getName() + " Invalid format. " + e.getMessage());
377 editContext.setOpenedFile(null);
378 editContext.setEditingSelected(false);
384 private boolean saveAndHandleError() {
388 catch (Exception e1) {
389 JOptionPane.showMessageDialog(ComponentPresetEditor.this, e1.getLocalizedMessage(),
390 "Error saving ORC file.", JOptionPane.ERROR_MESSAGE);
396 * Save the contents of the table model as XML in .orc format.
398 * @return true if the file was written
400 * @throws JAXBException thrown if the data could not be marshaled
401 * @throws IOException thrown if there was a problem with writing the file
403 private boolean saveAsORC() throws JAXBException, IOException {
406 final JFileChooser chooser = new JFileChooser();
407 chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
409 chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
410 if (editContext.getOpenedFile() != null) {
411 chooser.setSelectedFile(editContext.getOpenedFile());
414 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
417 int option = chooser.showSaveDialog(ComponentPresetEditor.this);
418 if (option != JFileChooser.APPROVE_OPTION) {
419 log.user("User decided not to save, option=" + option);
423 file = chooser.getSelectedFile();
425 log.user("User did not select a file");
429 ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
431 file = FileHelper.forceExtension(file, "orc");
433 MaterialHolder materials = new MaterialHolder();
434 List<ComponentPreset> presets = new ArrayList<ComponentPreset>();
436 for (int x = 0; x < model.getRowCount(); x++) {
437 ComponentPreset preset = (ComponentPreset) model.getAssociatedObject(x);
438 // If we don't have a material already defined for saving...
439 if ( materials.getMaterial(preset.get(ComponentPreset.MATERIAL)) == null ) {
440 // Check if we loaded a material with this name.
441 Material m = editContext.getMaterialsLoaded().getMaterial(preset.get(ComponentPreset.MATERIAL));
442 // If there was no material loaded with that name, use the component's material.
444 m = preset.get(ComponentPreset.MATERIAL);
451 return FileHelper.confirmWrite(file, this) && new OpenRocketComponentSaver().save(file, new ArrayList<Material>(materials.values()), presets);
454 class OpenedFileContext {
457 * State variable to keep track of which file was opened, in case it needs to be saved back to that file.
459 private File openedFile = null;
462 * Last directory; file chooser is set here so user doesn't have to keep navigating to a common area.
464 private File lastDirectory = null;
466 private boolean editingSelected = false;
468 private MaterialHolder materialsLoaded = null;
470 OpenedFileContext() {
473 public File getOpenedFile() {
477 public void setOpenedFile(final File theOpenedFile) {
478 openedFile = theOpenedFile;
481 public File getLastDirectory() {
482 return lastDirectory;
485 public void setLastDirectory(final File theLastDirectory) {
486 lastDirectory = theLastDirectory;
489 public boolean isEditingSelected() {
490 return editingSelected;
493 public void setEditingSelected(final boolean theEditingSelected) {
494 editingSelected = theEditingSelected;
497 public MaterialHolder getMaterialsLoaded() {
498 return materialsLoaded;
501 public void setMaterialsLoaded(final MaterialHolder theMaterialsLoaded) {
502 materialsLoaded = theMaterialsLoaded;