1 package net.sf.openrocket.gui.preset;
3 import net.miginfocom.swing.MigLayout;
4 import net.sf.openrocket.gui.util.FileHelper;
5 import net.sf.openrocket.gui.util.Icons;
6 import net.sf.openrocket.gui.util.SwingPreferences;
7 import net.sf.openrocket.l10n.ResourceBundleTranslator;
8 import net.sf.openrocket.logging.LogHelper;
9 import net.sf.openrocket.material.Material;
10 import net.sf.openrocket.preset.ComponentPreset;
11 import net.sf.openrocket.preset.loader.MaterialHolder;
12 import net.sf.openrocket.preset.loader.RocksimComponentFileTranslator;
13 import net.sf.openrocket.preset.xml.OpenRocketComponentLoader;
14 import net.sf.openrocket.preset.xml.OpenRocketComponentSaver;
15 import net.sf.openrocket.startup.Application;
17 import javax.swing.AbstractAction;
18 import javax.swing.Action;
19 import javax.swing.JButton;
20 import javax.swing.JDialog;
21 import javax.swing.JFileChooser;
22 import javax.swing.JFrame;
23 import javax.swing.JLabel;
24 import javax.swing.JMenu;
25 import javax.swing.JMenuBar;
26 import javax.swing.JMenuItem;
27 import javax.swing.JOptionPane;
28 import javax.swing.JPanel;
29 import javax.swing.JScrollPane;
30 import javax.swing.JSeparator;
31 import javax.swing.JTable;
32 import javax.swing.table.DefaultTableModel;
33 import javax.xml.bind.JAXBException;
34 import java.awt.event.ActionEvent;
35 import java.awt.event.ActionListener;
36 import java.awt.event.MouseAdapter;
37 import java.awt.event.MouseEvent;
39 import java.io.FileInputStream;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.List;
45 * A UI for editing component presets. Currently this is a standalone application - run the main within this class.
46 * TODO: Full I18n TODO: Save As .csv
48 public class ComponentPresetPanel extends JPanel implements PresetResultListener {
53 private static final LogHelper log = Application.getLogger();
56 * The I18N translator.
58 private static ResourceBundleTranslator trans = null;
61 * State variable to keep track of which file was opened, in case it needs to be saved back to that file.
63 private File openedFile = null;
66 * Last directory; file chooser is set here so user doesn't have to keep navigating to a common area.
68 private File lastDirectory = null;
71 * The table of presets.
76 * The table's data model.
78 private DataTableModel model;
81 * Flag that indicates if an existing Preset is currently being edited.
83 private boolean editingSelected = false;
86 trans = new ResourceBundleTranslator("l10n.messages");
87 net.sf.openrocket.startup.Application.setBaseTranslator(trans);
93 * @param frame the parent window
95 public ComponentPresetPanel(final JFrame frame) {
96 setLayout(new MigLayout("", "[82.00px, grow][168.00px, grow][84px, grow][117.00px, grow][][222px]",
97 "[346.00px, grow][29px]"));
99 model = new DataTableModel(new String[]{"Manufacturer", "Type", "Part No", "Description", ""});
101 table = new JTable(model);
102 table.getTableHeader().setFont(new JLabel().getFont());
103 //The action never gets called because the table MouseAdapter intercepts it first. Still need an empty
105 Action action = new AbstractAction() {
107 public void actionPerformed(final ActionEvent e) {
110 //Create a editor/renderer for the delete operation. Instantiation self-registers into the table.
111 new ButtonColumn(table, action, 4);
112 table.getColumnModel().getColumn(4).setMaxWidth(Icons.EDIT_DELETE.getIconWidth());
113 table.getColumnModel().getColumn(4).setMinWidth(Icons.EDIT_DELETE.getIconWidth());
115 JScrollPane scrollPane = new JScrollPane(table);
116 table.setFillsViewportHeight(true);
117 table.setAutoCreateRowSorter(true);
118 add(scrollPane, "cell 0 0 6 1,grow");
120 table.addMouseListener(new MouseAdapter() {
121 public void mouseClicked(MouseEvent e) {
122 JTable target = (JTable) e.getSource();
123 if (target.getSelectedColumn() == 4) {
124 if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(ComponentPresetPanel.this,
125 "Do you want to delete this preset?",
126 "Confirm Delete", JOptionPane.YES_OPTION,
127 JOptionPane.QUESTION_MESSAGE)) {
128 model.removeRow(target.getSelectedRow());
132 if (e.getClickCount() == 2) {
133 int row = target.getSelectedRow();
134 editingSelected = true;
135 new PresetEditorDialog(ComponentPresetPanel.this, (ComponentPreset) model.getAssociatedObject(row)).setVisible(true);
142 JMenuBar menuBar = new JMenuBar();
143 frame.setJMenuBar(menuBar);
145 JMenu mnFile = new JMenu("File");
148 JMenuItem mntmOpen = new JMenuItem("Open...");
149 mnFile.add(mntmOpen);
150 mntmOpen.addActionListener(new ActionListener() {
152 public void actionPerformed(final ActionEvent e) {
153 if (model.getRowCount() > 0) {
155 * If the table model already contains presets, ask the user if they a) want to discard those
156 * presets, b) save them before reading in another component file, or c) merge the read component file
157 * with the current contents of the table model.
159 Object[] options = {"Save",
163 int n = JOptionPane.showOptionDialog(frame,
164 "The editor contains existing component presets. What would you like to do with them?",
165 "Existing Component Presets",
166 JOptionPane.YES_NO_CANCEL_OPTION,
167 JOptionPane.QUESTION_MESSAGE,
171 if (n == 0) { //Save. Then remove existing rows and open.
172 if (saveAndHandleError()) {
173 model.removeAllRows();
175 else { //Save failed; bail out.
179 else if (n == 2) { //Discard and open
180 model.removeAllRows();
182 else if (n == 3) { //Cancel. Bail out.
191 JSeparator separator = new JSeparator();
192 mnFile.add(separator);
194 JMenuItem mntmSave = new JMenuItem("Save As...");
195 mnFile.add(mntmSave);
196 mntmSave.addActionListener(new ActionListener() {
198 public void actionPerformed(final ActionEvent e) {
199 saveAndHandleError();
203 JSeparator separator_1 = new JSeparator();
204 mnFile.add(separator_1);
206 JMenuItem mntmExit = new JMenuItem("Exit");
207 mnFile.add(mntmExit);
208 mntmExit.addActionListener(new ActionListener() {
209 public void actionPerformed(ActionEvent arg0) {
215 JButton addBtn = new JButton("Add");
216 addBtn.addMouseListener(new MouseAdapter() {
218 public void mouseClicked(MouseEvent arg0) {
219 editingSelected = false;
220 new PresetEditorDialog(ComponentPresetPanel.this).setVisible(true);
223 add(addBtn, "cell 0 1,alignx left,aligny top");
227 * Callback method from the PresetEditorDialog to notify this class when a preset has been saved. The 'save' is
228 * really just a call back here so the preset can be added to the master table. It's not to be confused with the
231 * @param preset the new or modified preset
234 public void notifyResult(final ComponentPreset preset) {
235 if (preset != null) {
236 DataTableModel model = (DataTableModel) table.getModel();
237 //Is this a new preset?
238 String description = preset.has(ComponentPreset.DESCRIPTION) ? preset.get(ComponentPreset.DESCRIPTION) :
240 if (!editingSelected) {
241 model.addRow(new Object[]{preset.getManufacturer().getDisplayName(), preset.getType().name(),
242 preset.getPartNo(), description, Icons.EDIT_DELETE}, preset);
245 //This is a modified preset; update all of the columns and the stored associated instance.
246 int row = table.getSelectedRow();
247 model.setValueAt(preset.getManufacturer().getDisplayName(), row, 0);
248 model.setValueAt(preset.getType().name(), row, 1);
249 model.setValueAt(preset.getPartNo(), row, 2);
250 model.setValueAt(description, row, 3);
251 model.associated.set(row, preset);
254 editingSelected = false;
258 * Launch the test main.
260 public static void main(String[] args) {
262 Application.setPreferences(new SwingPreferences());
263 JFrame dialog = new JFrame();
264 dialog.getContentPane().add(new ComponentPresetPanel(dialog));
265 dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
267 dialog.setVisible(true);
269 catch (Exception e) {
275 * A table model that adds associated objects to each row, allowing for easy retrieval.
277 class DataTableModel extends DefaultTableModel {
279 private List<Object> associated = new ArrayList<Object>();
282 * Constructs a <code>DefaultTableModel</code> with as many columns as there are elements in
283 * <code>columnNames</code> and <code>rowCount</code> of <code>null</code> object values. Each column's name
284 * will be taken from the <code>columnNames</code> array.
286 * @param columnNames <code>array</code> containing the names of the new columns; if this is <code>null</code>
287 * then the model has no columns
289 * @see #setDataVector
292 DataTableModel(final Object[] columnNames) {
293 super(columnNames, 0);
296 public void addRow(Object[] data, Object associatedData) {
298 associated.add(getRowCount() - 1, associatedData);
301 public void removeAllRows() {
302 for (int x = getRowCount(); x > 0; x--) {
303 super.removeRow(x - 1);
308 public void removeRow(int row) {
309 super.removeRow(row);
310 associated.remove(row);
313 public Object getAssociatedObject(int row) {
314 return associated.get(row);
317 public boolean isCellEditable(int rowIndex, int mColIndex) {
323 * Open the component file. Present a chooser for the user to navigate to the file.
325 * @return true if the file was successfully opened; Note: side effect, is that the ComponentPresets read from the
326 * file are written to the table model.
328 private boolean openComponentFile() {
329 final JFileChooser chooser = new JFileChooser();
330 chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
331 chooser.addChoosableFileFilter(FileHelper.CSV_FILE_FILTER);
332 chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
333 chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
334 if (lastDirectory != null) {
335 chooser.setCurrentDirectory(lastDirectory);
338 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
341 int option = chooser.showOpenDialog(ComponentPresetPanel.this);
342 if (option != JFileChooser.APPROVE_OPTION) {
344 log.user("User decided not to open, option=" + option);
348 File file = chooser.getSelectedFile();
351 log.user("User did not select a file");
355 lastDirectory = file.getParentFile();
356 List<ComponentPreset> presets = null;
358 if (file.getName().toLowerCase().endsWith(".orc")) {
359 presets = (List<ComponentPreset>) new OpenRocketComponentLoader().load(new FileInputStream(file), file.getName());
362 if (file.getName().toLowerCase().endsWith(".csv")) {
363 file = file.getParentFile();
365 presets = new ArrayList<ComponentPreset>();
366 MaterialHolder materialHolder = RocksimComponentFileTranslator.loadAll(presets, file);
368 if (presets != null) {
369 for (ComponentPreset next : presets) {
375 catch (Exception e) {
377 JOptionPane.showMessageDialog(ComponentPresetPanel.this, "Unable to open OpenRocket component file: " +
378 file.getName() + " Invalid format. " + e.getMessage());
385 private boolean saveAndHandleError() {
389 catch (Exception e1) {
390 JOptionPane.showMessageDialog(ComponentPresetPanel.this, e1.getLocalizedMessage(),
391 "Error saving ORC file.", JOptionPane.ERROR_MESSAGE);
397 * Save the contents of the table model as XML in .orc format.
399 * @return true if the file was written
401 * @throws JAXBException thrown if the data could not be marshaled
402 * @throws IOException thrown if there was a problem with writing the file
404 private boolean saveAsORC() throws JAXBException, IOException {
407 final JFileChooser chooser = new JFileChooser();
408 chooser.addChoosableFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
410 chooser.setFileFilter(FileHelper.OPEN_ROCKET_COMPONENT_FILTER);
411 if (openedFile != null) {
412 chooser.setSelectedFile(openedFile);
415 chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
418 int option = chooser.showSaveDialog(ComponentPresetPanel.this);
419 if (option != JFileChooser.APPROVE_OPTION) {
420 log.user("User decided not to save, option=" + option);
424 file = chooser.getSelectedFile();
426 log.user("User did not select a file");
430 ((SwingPreferences) Application.getPreferences()).setDefaultDirectory(chooser.getCurrentDirectory());
432 file = FileHelper.forceExtension(file, "orc");
434 List<Material> materials = new ArrayList<Material>();
435 List<ComponentPreset> presets = new ArrayList<ComponentPreset>();
437 for (int x = 0; x < model.getRowCount(); x++) {
438 ComponentPreset preset = (ComponentPreset) model.getAssociatedObject(x);
439 if (!materials.contains(preset.get(ComponentPreset.MATERIAL))) {
440 materials.add(preset.get(ComponentPreset.MATERIAL));
445 return FileHelper.confirmWrite(file, this) && new OpenRocketComponentSaver().save(file, materials, presets);