001package jahuwaldt.js.unit;
002
003import jahuwaldt.swing.JButtonGroup;
004import java.awt.Component;
005import java.awt.Dimension;
006import java.awt.event.ActionEvent;
007import java.awt.event.ActionListener;
008import java.util.ResourceBundle;
009import java.util.logging.Level;
010import java.util.logging.Logger;
011import javax.measure.converter.ConversionException;
012import javax.measure.unit.Unit;
013import javax.swing.*;
014import javax.swing.table.AbstractTableModel;
015import javax.swing.table.TableCellRenderer;
016import javax.swing.table.TableColumn;
017
018/**
019 * A panel the allows the user to select a set of units to be used.
020 * 
021 * <p> Modified by: Joseph A. Huwaldt </p>
022 * 
023 * @author Joseph A. Huwaldt, Date: October 14, 2008
024 * @version March 17, 2017
025 */
026@SuppressWarnings("serial")
027public class EditUnitSetPanel extends JPanel {
028
029    /**
030     * The resource bundle for use in this class.
031     */
032    static final ResourceBundle RESOURCES = ResourceBundle.getBundle("jahuwaldt.js.unit.EditUnitSetResources", java.util.Locale.getDefault());
033    
034    //  The column names in the derived units table.
035    private static final String[] derColumnNames = {
036        RESOURCES.getString("unitTypeHeaderLabel"), 
037        RESOURCES.getString("unitValueHeaderLabel")
038    };
039
040    //  The combo-boxes that contain the fundamental units.
041    private JComboBox timeCBox, lengthCBox, massCBox;
042
043    //  The data stored in the derived units table.
044    private final Object[][] derRowData = new Object[16][2];
045
046    //  The JTable displaying the derived units.
047    private final JTable derTable;
048
049    //  The UnitSet being edited.
050    private UnitSet theUnits = null;
051
052    /**
053     * Construct a panel that allows the user to select a set of units to be used.
054     *
055     * @param message    A message to display telling the user what to do.
056     * @param unitSet    The unit set being edited.
057     * @param showCustom The option for custom units is shown if this is true.
058     */
059    @SuppressWarnings("OverridableMethodCallInConstructor")
060    public EditUnitSetPanel(String message, UnitSet unitSet, boolean showCustom) {
061        super();
062
063        theUnits = (UnitSet)unitSet.clone();
064
065        //  Create the data to display in the derivative units table.
066        setDerivedData();
067
068        //  Layout the panel.
069        this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
070        this.setAlignmentX(Component.CENTER_ALIGNMENT);
071
072        //  Add instructions at the top.
073        Box topPanel = Box.createHorizontalBox();
074        topPanel.setAlignmentX(Component.RIGHT_ALIGNMENT);
075        this.add(topPanel);
076
077        topPanel.add(Box.createHorizontalStrut(10));
078        topPanel.add(new JLabel(message));
079
080        //  Create a panel to display in the center.
081        Box centerPanel = Box.createVerticalBox();
082        centerPanel.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
083        this.add(centerPanel);
084
085        //  Create a set of radio buttons for selecting the unit set to use.
086        Box panel = Box.createVerticalBox();
087        panel.setAlignmentX(Component.CENTER_ALIGNMENT);
088        centerPanel.add(panel);
089        panel.setBorder(BorderFactory.createTitledBorder(RESOURCES.getString("coherentUnitSetsTitle")));
090        JButtonGroup btnGroup = new JButtonGroup();
091
092        JRadioButton rb = new JRadioButton(RESOURCES.getString("siUnitSetTxt"));
093        panel.add(rb);
094        boolean found = false;
095        if (unitSet.equals(new UnitSet(UnitSet.UnitSystem.SI_MKS))) {
096            rb.setSelected(true);
097            found = true;
098        }
099        rb.addActionListener(new ActionListener() {
100            @Override
101            public void actionPerformed(ActionEvent e) {
102                theUnits = new UnitSet(UnitSet.UnitSystem.SI_MKS);
103                updateDerivativesTable();
104                setEnableFundamentalUnitBoxes(false);
105                setFundamentalUnitSelections();
106            }
107        });
108        btnGroup.add(rb);
109
110        rb = new JRadioButton(RESOURCES.getString("cgsUnitSetTxt"));
111        panel.add(rb);
112        if (!found && unitSet.equals(new UnitSet(UnitSet.UnitSystem.CGS))) {
113            rb.setSelected(true);
114            found = true;
115        }
116        rb.addActionListener(new ActionListener() {
117            @Override
118            public void actionPerformed(ActionEvent e) {
119                theUnits = new UnitSet(UnitSet.UnitSystem.CGS);
120                updateDerivativesTable();
121                setEnableFundamentalUnitBoxes(false);
122                setFundamentalUnitSelections();
123            }
124        });
125        btnGroup.add(rb);
126
127        rb = new JRadioButton(RESOURCES.getString("fssUnitSetTxt"));
128        panel.add(rb);
129        if (!found && unitSet.equals(new UnitSet(UnitSet.UnitSystem.US_FSS))) {
130            rb.setSelected(true);
131            found = true;
132        }
133        rb.addActionListener(new ActionListener() {
134            @Override
135            public void actionPerformed(ActionEvent e) {
136                theUnits = new UnitSet(UnitSet.UnitSystem.US_FSS);
137                updateDerivativesTable();
138                setEnableFundamentalUnitBoxes(false);
139                setFundamentalUnitSelections();
140            }
141        });
142        btnGroup.add(rb);
143
144        rb = new JRadioButton(RESOURCES.getString("fpsUnitSetTxt"));
145        panel.add(rb);
146        if (!found && unitSet.equals(new UnitSet(UnitSet.UnitSystem.US_FPS))) {
147            rb.setSelected(true);
148            found = true;
149        }
150        rb.addActionListener(new ActionListener() {
151            @Override
152            public void actionPerformed(ActionEvent e) {
153                theUnits = new UnitSet(UnitSet.UnitSystem.US_FPS);
154                updateDerivativesTable();
155                setEnableFundamentalUnitBoxes(false);
156                setFundamentalUnitSelections();
157            }
158        });
159        btnGroup.add(rb);
160
161        if (showCustom) {
162            rb = new JRadioButton(RESOURCES.getString("customUnitSetTxt"));
163            panel.add(rb);
164            if (!found)
165                rb.setSelected(true);
166            rb.addActionListener(new ActionListener() {
167                @Override
168                public void actionPerformed(ActionEvent e) {
169                    setEnableFundamentalUnitBoxes(true);
170                }
171            });
172            btnGroup.add(rb);
173
174            //  Create a series of combo-boxes that allow the user to choose custom combinations.
175            centerPanel.add(Box.createVerticalStrut(10));
176            panel = Box.createHorizontalBox();
177            panel.setBorder(BorderFactory.createTitledBorder(RESOURCES.getString("fundamentalUnitOptionsLabel")));
178            centerPanel.add(panel);
179
180            panel.add(new JLabel(RESOURCES.getString("timeLabel") + "=", JLabel.RIGHT));
181            timeCBox = new JComboBox(UnitSet.getSet(UnitSet.SetType.TIME));
182            timeCBox.setMaximumSize(timeCBox.getPreferredSize());
183            timeCBox.addActionListener(new ActionListener() {
184                @Override
185                public void actionPerformed(ActionEvent e) {
186                    JComboBox cbox = (JComboBox)e.getSource();
187                    int value = cbox.getSelectedIndex();
188                    if (value >= 0) {
189                        handleUnitChange(0, (Unit)cbox.getSelectedItem());
190                    }
191                }
192            });
193            panel.add(timeCBox);
194
195            panel.add(new JLabel(RESOURCES.getString("lengthLabel") + "=", JLabel.RIGHT));
196            lengthCBox = new JComboBox(UnitSet.getSet(UnitSet.SetType.LENGTH));
197            lengthCBox.setMaximumSize(lengthCBox.getPreferredSize());
198            lengthCBox.addActionListener(new ActionListener() {
199                @Override
200                public void actionPerformed(ActionEvent e) {
201                    JComboBox cbox = (JComboBox)e.getSource();
202                    int value = cbox.getSelectedIndex();
203                    if (value >= 0) {
204                        handleUnitChange(1, (Unit)cbox.getSelectedItem());
205                    }
206                }
207            });
208            panel.add(lengthCBox);
209
210            panel.add(new JLabel(RESOURCES.getString("massLabel") + "=", JLabel.RIGHT));
211            massCBox = new JComboBox(UnitSet.getSet(UnitSet.SetType.MASS));
212            massCBox.setMaximumSize(massCBox.getPreferredSize());
213            massCBox.addActionListener(new ActionListener() {
214                @Override
215                public void actionPerformed(ActionEvent e) {
216                    JComboBox cbox = (JComboBox)e.getSource();
217                    int value = cbox.getSelectedIndex();
218                    if (value >= 0) {
219                        handleUnitChange(2, (Unit)cbox.getSelectedItem());
220                    }
221                }
222            });
223            panel.add(massCBox);
224        }
225
226        //  Create a display of the derived units.
227        centerPanel.add(Box.createVerticalStrut(10));
228        panel = Box.createHorizontalBox();
229        panel.setBorder(BorderFactory.createTitledBorder(RESOURCES.getString("derivedUnitsLabel")));
230        centerPanel.add(panel);
231
232        //  Create a table to display the derived units.
233        derTable = new JTable(new DerivativeTableModel());
234        derTable.setCellSelectionEnabled(false);
235        derTable.setColumnSelectionAllowed(false);
236        derTable.setRowSelectionAllowed(false);
237        derTable.setCellEditor(null);
238        derTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);     //  Show scroll bars.
239
240        //  Initialize the column widths to show all the data.
241        initColumnWidths(derTable);
242
243        //  Determine the table preferred width.
244        int width = 0;
245        int numColumns = derTable.getColumnCount();
246        for (int i = 0; i < numColumns; ++i) {
247            TableColumn column = derTable.getColumnModel().getColumn(i);
248            width += column.getPreferredWidth();
249        }
250        derTable.setPreferredScrollableViewportSize(new Dimension(width, 110));
251
252        //  Put the table in a scroll pane so that we have scroll bars.
253        JScrollPane scrollPane = new JScrollPane(derTable);
254        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
255        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
256        scrollPane.setMaximumSize(new Dimension(width + 20, 250));
257
258        //  Add the list to the panel.
259        panel.add(scrollPane);
260
261        //  Set the intially selected items.
262        setFundamentalUnitSelections();
263
264        //  Enable/disable the unit boxes as appropriate.
265        setEnableFundamentalUnitBoxes(!found);
266    }
267
268    /**
269     * Method that handles the user changing one of the fundamental unit types. This is
270     * called whenever the user selects a new unit.
271     */
272    private void handleUnitChange(int idx, Unit newUnit) {
273
274        try {
275
276            switch (idx) {
277                case 0:     //  Change in time units.
278                    if (!newUnit.equals(theUnits.time()))
279                        theUnits.setTime(newUnit);
280                    break;
281
282                case 1:     //  Change in length units.
283                    if (!newUnit.equals(theUnits.length()))
284                        theUnits.setLength(newUnit);
285                    break;
286
287                case 2:     //  Change in mass units.
288                    if (!newUnit.equals(theUnits.mass()))
289                        theUnits.setMass(newUnit);
290                    break;
291            }
292
293            //  Create a coherent unit set.
294            theUnits.makeConsistent();
295
296            //  Update the derived units table.
297            updateDerivativesTable();
298
299        } catch (ConversionException ex) {
300            Logger.getLogger(EditUnitSetPanel.class.getName()).log(Level.SEVERE, null, ex);
301        }
302    }
303
304    /**
305     * Method that updates the derived unit table display.
306     */
307    private void updateDerivativesTable() {
308        //  Change the derived table data set.
309        setDerivedData();
310
311        //  Notify the derived table that it's data has changed.
312        AbstractTableModel model = (AbstractTableModel)derTable.getModel();
313        model.fireTableDataChanged();
314    }
315
316    /**
317     * Enabled or disable all the fundamental unit combo-boxes.
318     */
319    private void setEnableFundamentalUnitBoxes(boolean enable) {
320        if (timeCBox != null)
321            timeCBox.setEnabled(enable);
322        if (lengthCBox != null)
323            lengthCBox.setEnabled(enable);
324        if (massCBox != null)
325            massCBox.setEnabled(enable);
326    }
327
328    /**
329     * Set the currently selected item in the fundamental unit boxes.
330     */
331    private void setFundamentalUnitSelections() {
332        if (timeCBox != null)
333            timeCBox.setSelectedItem(theUnits.time());
334        if (lengthCBox != null)
335            lengthCBox.setSelectedItem(theUnits.length());
336        if (massCBox != null)
337            massCBox.setSelectedItem(theUnits.mass());
338    }
339
340    /**
341     * Method that fills in the derived row data table with the current units.
342     */
343    private void setDerivedData() {
344        int row = 0;
345        derRowData[row][0] = RESOURCES.getString("areaLabel");      derRowData[row++][1] = theUnits.area();
346        derRowData[row][0] = RESOURCES.getString("volumeLabel");    derRowData[row++][1] = theUnits.volume();
347        derRowData[row][0] = RESOURCES.getString("forceLabel");     derRowData[row++][1] = theUnits.force();
348        derRowData[row][0] = RESOURCES.getString("inertiaLabel");   derRowData[row++][1] = theUnits.inertia();
349        derRowData[row][0] = RESOURCES.getString("densityLabel");   derRowData[row++][1] = theUnits.massDensity();
350    }
351
352    /**
353     * Returns the unit set stored with this panel.
354     *
355     * @return The unit set stored with this panel.
356     */
357    public UnitSet getUnits() {
358        return theUnits;
359    }
360
361    /**
362     * Method that resets the minimum column widths.
363     */
364    private static void initColumnWidths(JTable table) {
365        int numColumns = table.getColumnCount();
366        for (int i = 0; i < numColumns; ++i) {
367            //  Get each column.
368            TableColumn column = table.getColumnModel().getColumn(i);
369
370            //  Determine the header width.
371            int headerWidth = 0;
372            try {
373                TableCellRenderer renderer = column.getHeaderRenderer();
374                if (renderer == null)
375                    renderer = table.getTableHeader().getDefaultRenderer();
376
377                Component comp = renderer.
378                        getTableCellRendererComponent(
379                                null, column.getHeaderValue(),
380                                false, false, 0, 0);
381                headerWidth = comp.getPreferredSize().width;
382            } catch (NullPointerException e) {
383                e.printStackTrace();
384            }
385
386            if (i == 0)
387                column.setPreferredWidth(headerWidth + 10);
388            else
389                //  Set the minimum width to the larger of the header width
390                //  and 100 pixels.
391                column.setPreferredWidth(Math.max(100, headerWidth) + 10);
392        }
393    }
394
395    /**
396     * Table model for the derivative unit table.
397     */
398    private class DerivativeTableModel extends AbstractTableModel {
399
400        @Override
401        public String getColumnName(int col) {
402            return derColumnNames[col];
403        }
404
405        @Override
406        public int getRowCount() {
407            return derRowData.length;
408        }
409
410        @Override
411        public int getColumnCount() {
412            return derColumnNames.length;
413        }
414
415        @Override
416        public Object getValueAt(int row, int col) {
417            return derRowData[row][col];
418        }
419
420        @Override
421        public boolean isCellEditable(int row, int col) {
422            return false;
423        }
424
425        @Override
426        public Class getColumnClass(int c) {
427            return getValueAt(0, c).getClass();
428        }
429    }
430}