Added filter check boxes to limit the displayed components based on fitting with...
[debian/openrocket] / core / src / net / sf / openrocket / gui / dialogs / preset / ComponentPresetChooserDialog.java
1 package net.sf.openrocket.gui.dialogs.preset;
2
3
4 import java.awt.Dialog;
5 import java.awt.Window;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.ActionListener;
8 import java.awt.event.ItemEvent;
9 import java.awt.event.ItemListener;
10 import java.util.ArrayList;
11 import java.util.List;
12
13 import javax.swing.JButton;
14 import javax.swing.JCheckBox;
15 import javax.swing.JDialog;
16 import javax.swing.JLabel;
17 import javax.swing.JPanel;
18 import javax.swing.JScrollPane;
19 import javax.swing.JTable;
20 import javax.swing.JTextField;
21 import javax.swing.ListSelectionModel;
22 import javax.swing.RowFilter;
23 import javax.swing.event.DocumentEvent;
24 import javax.swing.event.DocumentListener;
25 import javax.swing.table.TableModel;
26 import javax.swing.table.TableRowSorter;
27
28 import net.miginfocom.swing.MigLayout;
29 import net.sf.openrocket.gui.adaptors.Column;
30 import net.sf.openrocket.gui.adaptors.ColumnTableModel;
31 import net.sf.openrocket.gui.util.GUIUtil;
32 import net.sf.openrocket.l10n.Translator;
33 import net.sf.openrocket.preset.ComponentPreset;
34 import net.sf.openrocket.preset.TypedKey;
35 import net.sf.openrocket.rocketcomponent.InternalComponent;
36 import net.sf.openrocket.rocketcomponent.RocketComponent;
37 import net.sf.openrocket.rocketcomponent.SymmetricComponent;
38 import net.sf.openrocket.startup.Application;
39 import net.sf.openrocket.unit.Value;
40
41 public class ComponentPresetChooserDialog extends JDialog {
42         
43         private static final Translator trans = Application.getTranslator();
44         
45         private final RocketComponent component;
46         
47         private final JTable componentSelectionTable;
48         private final TableRowSorter<TableModel> sorter;
49         private final JTextField filterText;
50         private final JCheckBox foreDiameterFilterCheckBox;
51         private final JCheckBox aftDiameterFilterCheckBox;
52         
53         /*
54          * outerDiamtereColumnIndex is the index of the column associated with the OUTER_DIAMETER
55          * field.  This index is needed by the matchOuterDiameterCheckBox to implement filtering.
56          */
57         int aftDiameterColumnIndex = -1;
58         int foreDiameterColumnIndex = -1;
59
60         private final List<ComponentPreset> presets;
61         
62         private boolean okClicked = false;
63         
64         
65         public ComponentPresetChooserDialog(Window owner, RocketComponent component) {
66                 super(owner, trans.get("title"), Dialog.ModalityType.APPLICATION_MODAL);
67                 this.component = component;
68                 
69                 final TypedKey<?>[] columnKeys = component.getPresetType().getDisplayedColumns();
70
71                 presets = Application.getComponentPresetDao().listForType(component.getPresetType());
72
73                 /*
74                  * Set up the Column Table model
75                  */
76                 final Column[] columns = new Column[columnKeys.length+1];
77                 
78                 columns[0] = new Column(trans.get("table.column.Favorite") ) {
79                         @Override
80                         public Object getValueAt(int row) {
81                                 return Boolean.valueOf(ComponentPresetChooserDialog.this.presets.get(row).isFavorite());
82                         }
83                         
84                         @Override
85                         public void setValueAt(int row, Object value) {
86                                 Application.getComponentPresetDao().setFavorite(ComponentPresetChooserDialog.this.presets.get(row), (Boolean) value);
87                         }
88
89                         @Override
90                         public Class<?> getColumnClass() {
91                                 return Boolean.class;
92                         }
93                         
94                 };
95
96                 for (int i = 0; i < columnKeys.length; i++) {
97                         final TypedKey<?> key = columnKeys[i];
98                         if ( key == ComponentPreset.OUTER_DIAMETER ) {
99                                 // magic +1 is because we have inserted the column for favorites above.
100                                 aftDiameterColumnIndex = i+1;
101                         }
102                         if ( key == ComponentPreset.FORE_OUTER_DIAMETER ) {
103                                 // magic +1 is because we have inserted the column for favorites above.
104                                 foreDiameterColumnIndex = i+1;
105                         }
106                         columns[i+1] = new Column(trans.get("table.column." + key.getName())) {
107                                 @Override
108                                 public Object getValueAt(int row) {
109                                         ComponentPreset preset = ComponentPresetChooserDialog.this.presets.get(row);
110                                         if ( ! preset.has(key) ) {
111                                                 return null;
112                                         }
113                                         Object value = preset.get(key);
114                                         if (key.getType() == Double.class && key.getUnitGroup() != null) {
115                                                 return new Value( (Double) value, key.getUnitGroup() );
116                                         } else {
117                                                 return value;
118                                         }
119                                 }
120                         };
121                 }
122                 
123                 /*
124                  * perhaps there is a better way for this.
125                  * 
126                  * This check basically says that if a component does not have a fore diameter, use the
127                  * outer_diameter when filtering.  The problem this introduced is when this dialog is
128                  * created for nose cones (which are aft of a body tube), you will be given the option
129                  * to filter based on matching fore diameter.
130                  */
131                 if ( foreDiameterColumnIndex < 0 ) {
132                         foreDiameterColumnIndex = aftDiameterColumnIndex;
133                 }
134                 
135                 ColumnTableModel tableModel = new ColumnTableModel(columns) {
136                         @Override
137                         public int getRowCount() {
138                                 return ComponentPresetChooserDialog.this.presets.size();
139                         }
140
141                         @Override
142                         public boolean isCellEditable(int rowIndex, int columnIndex) {
143                                 return columnIndex == 0;
144                         }
145                         
146                 };
147                 
148
149                 /*
150                  * Add filter by text.
151                  */
152                 JPanel panel = new JPanel(new MigLayout("fill"));
153                 JLabel filterLabel = new JLabel(trans.get("ComponentPresetChooserDialog.filter.label"));
154                 panel.add(filterLabel);
155                 filterText = new JTextField(15);
156                 panel.add(filterText,"growx, growy 0, wrap");
157                 filterText.getDocument().addDocumentListener(new DocumentListener() {
158                         @Override
159                         public void changedUpdate(DocumentEvent e) {
160                                 updateFilters();
161                         }
162                         @Override
163                         public void insertUpdate(DocumentEvent e) {
164                                 updateFilters();
165                         }
166                         @Override
167                         public void removeUpdate(DocumentEvent e) {
168                                 updateFilters();
169                         }
170                 });
171                 
172                 /*
173                  * Add filter by fore diameter
174                  */
175                 foreDiameterFilterCheckBox = new JCheckBox();
176                 foreDiameterFilterCheckBox.setText(trans.get("ComponentPresetChooserDialog.checkbox.filterForeDiameter"));
177                 panel.add(foreDiameterFilterCheckBox, "skip, span 2");
178                 foreDiameterFilterCheckBox.addItemListener( new ItemListener () {
179                         @Override
180                         public void itemStateChanged(ItemEvent e) {
181                                 updateFilters();
182                         }
183                 });
184
185                 /* hide the fore diameter filter if it is not applicable */
186                 if ( foreDiameterColumnIndex < 0 || component.getPreviousComponent() == null ) {
187                         foreDiameterFilterCheckBox.setVisible(false);
188                 }
189                 
190                 /*
191                  * Add filter by aft diameter
192                  */
193                 aftDiameterFilterCheckBox = new JCheckBox();
194                 aftDiameterFilterCheckBox.setText(trans.get("ComponentPresetChooserDialog.checkbox.filterAftDiameter"));
195                 panel.add(aftDiameterFilterCheckBox, "skip, span 2, wrap");
196                 aftDiameterFilterCheckBox.addItemListener( new ItemListener () {
197                         @Override
198                         public void itemStateChanged(ItemEvent e) {
199                                 updateFilters();
200                         }
201                 });
202
203                 /* hide the aft diameter filter if it is not applicable */
204                 if ( aftDiameterColumnIndex < 0 || component.getNextComponent() == null ) {
205                         aftDiameterFilterCheckBox.setVisible(false);
206                 }
207                 
208                 componentSelectionTable = new JTable( tableModel );
209                 
210                 componentSelectionTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
211
212                 sorter = new TableRowSorter<TableModel>(tableModel);
213                 componentSelectionTable.setRowSorter(sorter);
214
215                 JScrollPane scrollpane = new JScrollPane();
216                 scrollpane.setViewportView(componentSelectionTable);
217                 panel.add(scrollpane, "grow, width :500:, height :300:, spanx, wrap para");
218
219                 
220                 // OK / Cancel buttons
221                 JButton okButton = new JButton(trans.get("dlg.but.ok"));
222                 okButton.addActionListener(new ActionListener() {
223                         @Override
224                         public void actionPerformed(ActionEvent e) {
225                                 close(true);
226                         }
227                 });
228                 panel.add(okButton, "tag ok, spanx, split");
229                 
230                 //// Cancel button
231                 JButton cancelButton = new JButton(trans.get("dlg.but.cancel"));
232                 cancelButton.addActionListener(new ActionListener() {
233                         @Override
234                         public void actionPerformed(ActionEvent e) {
235                                 close(false);
236                         }
237                 });
238                 panel.add(cancelButton, "tag cancel");
239                 
240                 this.add(panel);
241                 
242                 this.setModal(true);
243                 this.pack();
244                 this.setLocationByPlatform(true);
245                 GUIUtil.setDisposableDialogOptions(this, okButton);
246                 
247                 //JComponent focus = selectionPanel.getDefaultFocus();
248                 //if (focus != null) {
249                 //      focus.grabFocus();
250                 //}
251                 
252                 // Set the closeable dialog after all initialization
253                 //selectionPanel.setCloseableDialog(this);
254         }
255         
256         /**
257          * Return the motor selected by this chooser dialog, or <code>null</code> if the selection has been aborted.
258          * 
259          * @return      the selected motor, or <code>null</code> if no motor has been selected or the selection was canceled.
260          */
261         public ComponentPreset getSelectedComponentPreset() {
262                 if (!okClicked)
263                         return null;
264                 int row = componentSelectionTable.getSelectedRow();
265                 row = componentSelectionTable.convertRowIndexToModel(row);
266                 return presets.get(row);
267         }
268         
269         public void close(boolean ok) {
270                 okClicked = ok;
271                 this.setVisible(false);
272         }
273         
274         private void updateFilters() {
275                 List<RowFilter<TableModel,Object>> filters = new ArrayList<RowFilter<TableModel,Object>> (2);
276                 String filterTextRegex = filterText.getText();
277                 if ( filterTextRegex != null ) {
278                         try {
279                                 // The "(?iu)" magic turns on case insensitivity with unicode chars
280                                 RowFilter<TableModel,Object> regexFilter = RowFilter.regexFilter("(?iu)"+filterTextRegex);
281                                 filters.add(regexFilter);
282                         } catch ( java.util.regex.PatternSyntaxException e ) {
283                         }
284                 }
285                 if ( aftDiameterFilterCheckBox.isSelected() ) {
286                         // FIXME - please verify this logic looks correct.
287                         // Grab the next component.
288                         // If this.component is an InternalComponent, then we want to filter the outer diameter field
289                         // against the next component's inner diameter.
290                         RocketComponent nextComponent = component.getNextComponent();
291                         if ( nextComponent != null && nextComponent instanceof SymmetricComponent ) {
292                                 SymmetricComponent parent = (SymmetricComponent) nextComponent;
293                                 double nextDiameter;
294                                 if ( this.component instanceof InternalComponent ) {
295                                         nextDiameter = parent.getInnerRadius(0) * 2.0;
296                                 } else {
297                                         nextDiameter = parent.getForeRadius() * 2.0;
298                                 }
299                                 RowFilter<TableModel,Object> outerDiameterFilter = new ComponentPresetRowFilter( nextDiameter, aftDiameterColumnIndex);
300                                 filters.add(outerDiameterFilter);
301                         }
302                 }
303                 if ( foreDiameterFilterCheckBox.isSelected() ) {
304                         // FIXME - please verify this logic looks correct.
305                         // Grab the previous component.
306                         // If this.component is an InternalComponent, then we want to filter the outer diameter field
307                         // against the previous component's inner diameter.
308                         RocketComponent previousComponent = component.getPreviousComponent();
309                         if ( previousComponent != null && previousComponent instanceof SymmetricComponent ) {
310                                 SymmetricComponent parent = (SymmetricComponent) previousComponent;
311                                 double previousDaimeter;
312                                 if ( this.component instanceof InternalComponent ) {
313                                         previousDaimeter = parent.getInnerRadius(parent.getLength()) * 2.0;
314                                 } else {
315                                         previousDaimeter = parent.getAftRadius() * 2.0;
316                                 }
317                                 RowFilter<TableModel,Object> outerDiameterFilter = new ComponentPresetRowFilter( previousDaimeter, foreDiameterColumnIndex);
318                                 filters.add(outerDiameterFilter);
319                         }
320                 }
321                 
322                 sorter.setRowFilter( RowFilter.andFilter(filters) );
323         }
324 }