Java tutorial
/* * RDV * Real-time Data Viewer * http://rdv.googlecode.com/ * * Copyright (c) 2005-2007 University at Buffalo * Copyright (c) 2005-2007 NEES Cyberinfrastructure Center * Copyright (c) 2008 Palta Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $URL$ * $Revision$ * $Date$ * $Author$ */ package org.rdv.datapanel; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.print.PrinterException; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.AbstractButton; import javax.swing.Box; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.SwingConstants; import javax.swing.TransferHandler; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; import javax.swing.event.MouseInputAdapter; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rdv.data.Channel; import org.rdv.rbnb.Player; import org.rdv.ui.ChannelListDataFlavor; import com.rbnb.sapi.ChannelMap; /** * A Data Panel extension to display numeric data in a tabular form. Maximum and * minimum values are also displayed. * * @author Jason P. Hanley * @author Lawrence J. Miller <ljmiller@sdsc.edu> * @since 1.3 */ public class DigitalTabularDataPanel extends AbstractDataPanel { static Log log = LogFactory.getLog(DigitalTabularDataPanel.class.getName()); /** * The main panel * * @since 1.3 */ private JPanel mainPanel; /** * The container for the tables */ private Box panelBox; /** * The maximum number of column groups */ private static final int MAX_COLUMN_GROUP_COUNT = 10; /** * The current number of column groups */ private int columnGroupCount; /** * The data models for the table */ private List<DataTableModel> tableModels; /** * The tables */ private List<JTable> tables; /** * A map from channels to table indexes */ private Map<String, Integer> channelTableMap; /** * The cell renderer for the data */ private DoubleTableCellRenderer doubleCellRenderer; private DataTableCellRenderer dataCellRenderer; /** * The button to set decimal data rendering */ private JToggleButton decimalButton; /** * The button to set engineering data renderering */ private JToggleButton engineeringButton; /** * The group of check box components to control the the min/max columns * visibility */ private CheckBoxGroup showMinMaxCheckBoxGroup; /** * The group of check box components to control the the threshold columns * visibility */ private CheckBoxGroup showThresholdCheckBoxGroup; /** * The last time data was displayed in the UI * * @since 1.2 */ double lastTimeDisplayed; /** * The maximum number of channels allowed in this data panel; */ private static final int MAX_CHANNELS_PER_COLUMN_GROUP = 40; /** * The percentage of the warning value, when a value gets within this much * of a threshold, a yellow background will be shown. */ private static final double WARNING_PERCENTAGE = 0.25; /** * The percentage of the critical value, when a value gets within this much * of a threshold, a red background will be shown. */ private static final double CRITICAL_PERCENTAGE = 0.1; private JCheckBox useOffsetsCheckBox; /** * Initialize the data panel. * * @since 1.2 */ public DigitalTabularDataPanel() { super(); tableModels = new ArrayList<DataTableModel>(); tables = new ArrayList<JTable>(); channelTableMap = new HashMap<String, Integer>(); lastTimeDisplayed = -1; initComponents(); setDataComponent(mainPanel); } /** * Initialize the container and add the header. * * @since 1.2 */ private void initComponents() { mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); panelBox = Box.createHorizontalBox(); dataCellRenderer = new DataTableCellRenderer(); doubleCellRenderer = new DoubleTableCellRenderer(); showMinMaxCheckBoxGroup = new CheckBoxGroup(); showThresholdCheckBoxGroup = new CheckBoxGroup(); addColumn(); mainPanel.add(panelBox, BorderLayout.CENTER); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BorderLayout()); JPanel offsetsPanel = new JPanel(); useOffsetsCheckBox = new JCheckBox("Use Offsets"); useOffsetsCheckBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { boolean checked = ((JCheckBox) ae.getSource()).isSelected(); useOffsetRenderer(checked); } }); offsetsPanel.add(useOffsetsCheckBox); JButton takeOffsetsButton = new JButton("Take Offsets"); takeOffsetsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { useOffsetsCheckBox.setSelected(true); // this moved from above // repaint for (int i = 0; i < columnGroupCount; i++) { ((DataTableModel) tableModels.get(i)).takeOffsets(); ((DataTableModel) tableModels.get(i)).useOffsets(true); ((JTable) tables.get(i)).repaint(); } // for } // actionPerformed () }); // actionlistener offsetsPanel.add(takeOffsetsButton); buttonPanel.add(offsetsPanel, BorderLayout.WEST); JPanel formatPanel = new JPanel(); decimalButton = new JToggleButton("Decimal", true); decimalButton.setSelected(true); decimalButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { useEngineeringRenderer(false); } }); formatPanel.add(decimalButton); engineeringButton = new JToggleButton("Engineering"); engineeringButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { useEngineeringRenderer(true); } }); formatPanel.add(engineeringButton); ButtonGroup formatButtonGroup = new ButtonGroup(); formatButtonGroup.add(decimalButton); formatButtonGroup.add(engineeringButton); buttonPanel.add(formatPanel, BorderLayout.EAST); mainPanel.add(buttonPanel, BorderLayout.SOUTH); mainPanel.addMouseListener(new MouseInputAdapter() { }); } private void addColumn() { if (columnGroupCount == MAX_COLUMN_GROUP_COUNT) { return; } if (columnGroupCount != 0) { panelBox.add(Box.createHorizontalStrut(7)); } final int tableIndex = columnGroupCount; final DataTableModel tableModel = new DataTableModel(); final JTable table = new JTable(tableModel); table.setDragEnabled(true); table.setName(DigitalTabularDataPanel.class.getName() + " JTable #" + Integer.toString(columnGroupCount)); if (showThresholdCheckBoxGroup.isSelected()) { tableModel.setThresholdVisible(true); } if (showMinMaxCheckBoxGroup.isSelected()) { tableModel.setMaxMinVisible(true); table.getColumn("Min").setCellRenderer(doubleCellRenderer); table.getColumn("Max").setCellRenderer(doubleCellRenderer); } table.getColumn("Value").setCellRenderer(dataCellRenderer); tables.add(table); tableModels.add(tableModel); JScrollPane tableScrollPane = new JScrollPane(table); panelBox.add(tableScrollPane); // popup menu for panel JPopupMenu popupMenu = new JPopupMenu(); final JMenuItem copyMenuItem = new JMenuItem("Copy"); copyMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { TransferHandler.getCopyAction().actionPerformed( new ActionEvent(table, ae.getID(), ae.getActionCommand(), ae.getWhen(), ae.getModifiers())); } }); popupMenu.add(copyMenuItem); popupMenu.addSeparator(); JMenuItem printMenuItem = new JMenuItem("Print..."); printMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { table.print(JTable.PrintMode.FIT_WIDTH); } catch (PrinterException pe) { } } }); popupMenu.add(printMenuItem); popupMenu.addSeparator(); final JCheckBoxMenuItem showMaxMinMenuItem = new JCheckBoxMenuItem("Show min/max columns", false); showMaxMinMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { setMaxMinVisible(showMaxMinMenuItem.isSelected()); } }); showMinMaxCheckBoxGroup.addCheckBox(showMaxMinMenuItem); popupMenu.add(showMaxMinMenuItem); final JCheckBoxMenuItem showThresholdMenuItem = new JCheckBoxMenuItem("Show threshold columns", false); showThresholdMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { setThresholdVisible(showThresholdMenuItem.isSelected()); } }); showThresholdCheckBoxGroup.addCheckBox(showThresholdMenuItem); popupMenu.add(showThresholdMenuItem); popupMenu.addSeparator(); JMenuItem blankRowMenuItem = new JMenuItem("Insert blank row"); blankRowMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { tableModel.addBlankRow(); } }); popupMenu.add(blankRowMenuItem); final JMenuItem removeSelectedRowsMenuItem = new JMenuItem("Remove selected rows"); removeSelectedRowsMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { removeSelectedRows(tableIndex); } }); popupMenu.add(removeSelectedRowsMenuItem); popupMenu.addSeparator(); JMenu numberOfColumnsMenu = new JMenu("Number of columns"); numberOfColumnsMenu.addMenuListener(new MenuListener() { public void menuSelected(MenuEvent me) { JMenu menu = (JMenu) me.getSource(); for (int j = 0; j < MAX_COLUMN_GROUP_COUNT; j++) { JMenuItem menuItem = menu.getItem(j); boolean selected = (j == (columnGroupCount - 1)); menuItem.setSelected(selected); } } public void menuDeselected(MenuEvent me) { } public void menuCanceled(MenuEvent me) { } }); for (int i = 0; i < MAX_COLUMN_GROUP_COUNT; i++) { final int number = i + 1; JRadioButtonMenuItem item = new JRadioButtonMenuItem(Integer.toString(number)); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { setNumberOfColumns(number); } }); numberOfColumnsMenu.add(item); } popupMenu.add(numberOfColumnsMenu); popupMenu.addPopupMenuListener(new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent arg0) { boolean anyRowsSelected = table.getSelectedRowCount() > 0; copyMenuItem.setEnabled(anyRowsSelected); removeSelectedRowsMenuItem.setEnabled(anyRowsSelected); } public void popupMenuWillBecomeInvisible(PopupMenuEvent arg0) { } public void popupMenuCanceled(PopupMenuEvent arg0) { } }); // set component popup and mouselistener to trigger it table.setComponentPopupMenu(popupMenu); tableScrollPane.setComponentPopupMenu(popupMenu); panelBox.revalidate(); columnGroupCount++; properties.setProperty("numberOfColumns", Integer.toString(columnGroupCount)); } private void removeColumn() { if (columnGroupCount == 1) { return; } columnGroupCount--; Iterator<String> i = channels.iterator(); while (i.hasNext()) { String channelName = i.next(); if (channelTableMap.get(channelName) == columnGroupCount) { removeChannel(channelName); } } tables.remove(columnGroupCount); tableModels.remove(columnGroupCount); panelBox.remove(columnGroupCount * 2); panelBox.remove(columnGroupCount * 2 - 1); panelBox.revalidate(); } private void setNumberOfColumns(int columns) { if (columnGroupCount < columns) { for (int i = columnGroupCount; i < columns; i++) { addColumn(); } } else if (columnGroupCount > columns) { for (int i = columnGroupCount; i > columns; i--) { removeColumn(); } } } /** * Remove all the rows in the specified table that are selected. If a * selected row is not blank, the channel in that row will be removed. * * @param tableIndex * the index of the table */ private void removeSelectedRows(int tableIndex) { List<String> selectedChannels = new ArrayList<String>(); JTable table = tables.get(tableIndex); int[] selectedRows = table.getSelectedRows(); table.clearSelection(); DataTableModel tableModel = tableModels.get(tableIndex); for (int j = selectedRows.length - 1; j >= 0; j--) { int row = selectedRows[j]; if (tableModel.isBlankRow(row)) { tableModel.deleteBlankRow(row); } else { DataRow dataRow = tableModel.getRowAt(row); selectedChannels.add(dataRow.getName()); } } for (String channelName : selectedChannels) { removeChannel(channelName); } } private void useOffsetRenderer(boolean useOffset) { useOffsetsCheckBox.setSelected(useOffset); for (int i = 0; i < columnGroupCount; i++) { ((DataTableModel) tableModels.get(i)).useOffsets(useOffset); ((JTable) tables.get(i)).repaint(); } if (useOffset) { properties.setProperty("useoffset", "true"); } else properties.setProperty("useoffset", "false"); } private void useEngineeringRenderer(boolean useEngineeringRenderer) { dataCellRenderer.setShowEngineeringFormat(useEngineeringRenderer); doubleCellRenderer.setShowEngineeringFormat(useEngineeringRenderer); for (JTable table : tables) { table.repaint(); } if (useEngineeringRenderer) { engineeringButton.setSelected(true); properties.setProperty("renderer", "engineering"); } else { decimalButton.setSelected(true); properties.remove("renderer"); } } private void setMaxMinVisible(boolean maxMinVisible) { if (showMinMaxCheckBoxGroup.isSelected() != maxMinVisible) { for (int i = 0; i < this.columnGroupCount; i++) { DataTableModel tableModel = tableModels.get(i); JTable table = tables.get(i); tableModel.setMaxMinVisible(maxMinVisible); table.getColumn("Value").setCellRenderer(dataCellRenderer); if (maxMinVisible) { table.getColumn("Min").setCellRenderer(doubleCellRenderer); table.getColumn("Max").setCellRenderer(doubleCellRenderer); } if (showThresholdCheckBoxGroup.isSelected()) { table.getColumn("Min Thresh").setCellRenderer(doubleCellRenderer); table.getColumn("Max Thresh").setCellRenderer(doubleCellRenderer); } } showMinMaxCheckBoxGroup.setSelected(maxMinVisible); if (maxMinVisible) { properties.setProperty("maxMinVisible", "true"); } else { properties.remove("maxMinVisible"); } } } private void setThresholdVisible(boolean thresholdVisible) { if (showThresholdCheckBoxGroup.isSelected() != thresholdVisible) { for (int i = 0; i < columnGroupCount; i++) { DataTableModel tableModel = tableModels.get(i); JTable table = tables.get(i); tableModel.setThresholdVisible(thresholdVisible); table.getColumn("Value").setCellRenderer(dataCellRenderer); if (showMinMaxCheckBoxGroup.isSelected()) { table.getColumn("Min").setCellRenderer(doubleCellRenderer); table.getColumn("Max").setCellRenderer(doubleCellRenderer); } if (thresholdVisible) { table.getColumn("Min Thresh").setCellRenderer(doubleCellRenderer); table.getColumn("Max Thresh").setCellRenderer(doubleCellRenderer); } } showThresholdCheckBoxGroup.setSelected(thresholdVisible); if (thresholdVisible) { properties.setProperty("thresholdVisible", "true"); } else { properties.remove("thresholdVisible"); } } } void clearData() { for (int i = 0; i < columnGroupCount; i++) { ((DataTableModel) tableModels.get(i)).clearData(); } lastTimeDisplayed = -1; } public boolean supportsMultipleChannels() { return true; } /** * an overridden method from * * @see org.rdv.datapanel.AbstractDataPanel for the * @see DropTargetListener interface */ @SuppressWarnings("unchecked") public void drop(DropTargetDropEvent e) { try { int dropAction = e.getDropAction(); if (dropAction == DnDConstants.ACTION_LINK) { DataFlavor channelListDataFlavor = new ChannelListDataFlavor(); Transferable tr = e.getTransferable(); if (e.isDataFlavorSupported(channelListDataFlavor)) { e.acceptDrop(DnDConstants.ACTION_LINK); e.dropComplete(true); // calculate which table the x coordinate of the mouse // corresponds to double clickX = e.getLocation().getX(); // get the mouse x // coordinate int compWidth = dataComponent.getWidth(); // gets the // width of this // component final int tableNum = (int) (clickX * columnGroupCount / compWidth); final List<String> channels = (List) tr.getTransferData(channelListDataFlavor); new Thread() { public void run() { for (int i = 0; i < channels.size(); i++) { String channel = channels.get(i); boolean status; if (supportsMultipleChannels()) { status = addChannel(channel, tableNum); } else { status = setChannel(channel); } if (!status) { // TODO display an error in the UI } } } }.start(); } else { e.rejectDrop(); } } } catch (IOException ioe) { ioe.printStackTrace(); } catch (UnsupportedFlavorException ufe) { ufe.printStackTrace(); } } // drop () public boolean addChannel(String channelName) { int tableNumber = 0; Integer index = channelTableMap.get(channelName); if (index != null) { tableNumber = index; } if (channelName.startsWith("BLANK ")) { tableNumber = Integer.parseInt(channelName.substring(6)); } return addChannel(channelName, tableNumber); } public boolean addChannel(String channelName, int tableNum) { if (tableNum >= MAX_COLUMN_GROUP_COUNT) { return false; } if (tableNum >= columnGroupCount) { setNumberOfColumns(tableNum + 1); } if (tableModels.get(tableNum).getRowCount() >= MAX_CHANNELS_PER_COLUMN_GROUP) { return false; } // add a blank row if (channelName.startsWith("BLANK ")) { tableModels.get(tableNum).addBlankRow(); return true; } if (channels.contains(channelName)) { return false; } channelTableMap.put(channelName, tableNum); if (super.addChannel(channelName)) { return true; } else { channelTableMap.remove(channelName); return false; } } protected void channelAdded(String channelName) { String labelText = channelName; Channel channel = rbnbController.getChannel(channelName); String unit = null; if (channel != null) { unit = channel.getUnit(); if (unit != null) { labelText += "(" + unit + ")"; } } String lowerThresholdString = (String) (lowerThresholds.get(channelName)); String upperThresholdString = (String) (upperThresholds.get(channelName)); // Double.NEGATIVE_INFINITY and Double.POSITIVE_INFINITY represent empty // thresholds double lowerThreshold = Double.NEGATIVE_INFINITY; double upperThreshold = Double.POSITIVE_INFINITY; // handle errors generated by daq - "Unknown command 'list-lowerbounds'" if (lowerThresholdString != null) { try { lowerThreshold = Double.parseDouble(lowerThresholdString); } catch (java.lang.NumberFormatException nfe) { log.warn("Non-numeric lower threshold in metadata: " + lowerThresholdString); } } // if if (upperThresholdString != null) { try { upperThreshold = Double.parseDouble(upperThresholdString); } catch (java.lang.NumberFormatException nfe) { log.warn("Non-numeric upper threshold in metadata: " + upperThresholdString); } } // if int tableNumber = channelTableMap.get(channelName); tableModels.get(tableNumber).addRow(channelName, unit, lowerThreshold, upperThreshold); properties.setProperty("channelTable_" + channelName, Integer.toString(tableNumber)); } protected void channelRemoved(String channelName) { int tableNumber = channelTableMap.get(channelName); tableModels.get(tableNumber).deleteRow(channelName); channelTableMap.remove(channelName); properties.remove("channelTable_" + channelName); properties.remove("minThreshold_" + channelName); properties.remove("maxThreshold_" + channelName); } public void postTime(double time) { if (time < this.time) { lastTimeDisplayed = -1; } super.postTime(time); for (int j = 0; j < columnGroupCount; j++) { boolean rowCleared = false; DataTableModel tableModel = (DataTableModel) tableModels.get(j); for (int k = 0; k < tableModel.getRowCount(); k++) { DataRow row = tableModel.getRowAt(k); if (row.isCleared()) { continue; } double timestamp = row.getTime(); if (timestamp <= time - timeScale || timestamp > time) { row.clearData(); rowCleared = true; } } if (rowCleared) { tableModel.fireTableDataChanged(); } } if (channelMap == null) { // no data to display yet return; } // loop over all channels and see if there is data for them Iterator<String> i = channels.iterator(); while (i.hasNext()) { String channelName = i.next(); int channelIndex = channelMap.GetIndex(channelName); // if there is data for channel, post it if (channelIndex != -1) { postDataTabular(channelName, channelIndex); } } if (state != Player.STATE_MONITORING) { lastTimeDisplayed = time; } } private void postDataTabular(String channelName, int channelIndex) { double[] times = channelMap.GetTimes(channelIndex); int startIndex = -1; // determine what time we should load data from double dataStartTime; if (lastTimeDisplayed == time) { dataStartTime = time - timeScale; } else { dataStartTime = lastTimeDisplayed; } for (int i = 0; i < times.length; i++) { if (times[i] > dataStartTime && times[i] <= time) { startIndex = i; break; } } // see if there is no data in the time range we are loooking at if (startIndex == -1) { return; } int endIndex = startIndex; for (int i = times.length - 1; i > startIndex; i--) { if (times[i] <= time) { endIndex = i; break; } } for (int i = startIndex; i <= endIndex; i++) { double data; int typeID = channelMap.GetType(channelIndex); switch (typeID) { case ChannelMap.TYPE_FLOAT64: data = channelMap.GetDataAsFloat64(channelIndex)[i]; break; case ChannelMap.TYPE_FLOAT32: data = channelMap.GetDataAsFloat32(channelIndex)[i]; break; case ChannelMap.TYPE_INT64: data = channelMap.GetDataAsInt64(channelIndex)[i]; break; case ChannelMap.TYPE_INT32: data = channelMap.GetDataAsInt32(channelIndex)[i]; break; case ChannelMap.TYPE_INT16: data = channelMap.GetDataAsInt16(channelIndex)[i]; break; case ChannelMap.TYPE_INT8: data = channelMap.GetDataAsInt8(channelIndex)[i]; break; case ChannelMap.TYPE_STRING: case ChannelMap.TYPE_UNKNOWN: case ChannelMap.TYPE_BYTEARRAY: default: return; } // switch for (int ii = 0; ii < columnGroupCount; ii++) { ((DataTableModel) tableModels.get(ii)).updateData(channelName, times[i], data); } // for } // for } // postDataTabular () public List<String> subscribedChannels() { List<String> allChannels = new ArrayList<String>(); for (int i = 0; i < tableModels.size(); i++) { DataTableModel tableModel = tableModels.get(i); for (int j = 0; j < tableModel.getRowCount(); j++) { DataRow dataRow = tableModel.getRowAt(j); String name = dataRow.getName(); if (name.length() == 0) { name = "BLANK " + i; } allChannels.add(name); } } return allChannels; } public void setProperty(String key, String value) { super.setProperty(key, value); if (key != null && value != null) { if (key.equals("renderer") && value.equals("engineering")) { useEngineeringRenderer(true); } else if (key.equals("maxMinVisible") && value.equals("true")) { setMaxMinVisible(true); } else if (key.equals("thresholdVisible") && value.equals("true")) { setThresholdVisible(true); } else if (key.equals("numberOfColumns")) { int columns = Integer.parseInt(value); setNumberOfColumns(columns); } else if (key.startsWith("channelTable_")) { String channelName = key.substring(13); int tableIndex = Integer.parseInt(value); channelTableMap.put(channelName, tableIndex); } else if (key.startsWith("minThreshold_")) { String channelName = key.substring(13); try { Double.parseDouble(value); lowerThresholds.put(channelName, value); properties.put(key, value); } catch (NumberFormatException e) { } } else if (key.startsWith("maxThreshold_")) { String channelName = key.substring(13); try { Double.parseDouble(value); upperThresholds.put(channelName, value); properties.put(key, value); } catch (NumberFormatException e) { } } else if (key.startsWith("useoffset") && value.equals("true")) { useOffsetRenderer(true); } } // if } // setProperty () public String toString() { return "Tabular Data Panel"; } /** an inner class to implement the data model in this design pattern */ private class DataTableModel extends AbstractTableModel { /** serialization version identifier */ private static final long serialVersionUID = 1706987603986659555L; private String[] columnNames = { "Name", "Value", "Unit", "Min", "Max", "Min Thresh", "Max Thresh" }; private ArrayList<DataRow> rows; private boolean cleared; private boolean maxMinVisible; private boolean thresholdVisible; private boolean useOffsets; private final DataRow BLANK_ROW = new DataRow(new String(), null, (Double.NEGATIVE_INFINITY), Double.POSITIVE_INFINITY); public DataTableModel() { super(); rows = new ArrayList<DataRow>(); cleared = true; maxMinVisible = false; thresholdVisible = false; useOffsets = false; } public boolean isCellEditable(int row, int col) { return (col == this.findColumn("Min Thresh") || col == this.findColumn("Max Thresh")); } // isCellEditable () public void addRow(String name, String unit, double lowerThresh, double upperThresh) { addRow(new DataRow(name, unit, lowerThresh, upperThresh)); } public void addBlankRow() { addRow(BLANK_ROW); } public boolean isBlankRow(int row) { return rows.get(row) == BLANK_ROW; } public void deleteBlankRow(int row) { if (isBlankRow(row)) { rows.remove(row); fireTableRowsDeleted(row, row); } } public void addRow(DataRow dataRow) { rows.add(dataRow); int row = rows.size() - 1; fireTableRowsInserted(row, row); } public void deleteRow(String name) { for (int i = 0; i < rows.size(); i++) { DataRow dataRow = (DataRow) rows.get(i); if (dataRow.name.equals(name)) { rows.remove(dataRow); this.fireTableRowsDeleted(i, i); break; } } } public void updateData(String name, double time, double data) { cleared = false; String chanUnit = null; Channel channel = rbnbController.getChannel(name); if (channel != null) chanUnit = channel.getUnit(); for (int i = 0; i < rows.size(); i++) { DataRow dataRow = (DataRow) rows.get(i); if (dataRow.name.equals(name)) { dataRow.setData(time, data, chanUnit); fireTableDataChanged(); break; } } } public void clearData() { cleared = true; for (int i = 0; i < rows.size(); i++) { DataRow dataRow = (DataRow) rows.get(i); dataRow.clearData(); } fireTableDataChanged(); } public void takeOffsets() { for (int i = 0; i < rows.size(); i++) { DataRow dataRow = (DataRow) rows.get(i); dataRow.setOffset(dataRow.getData()); } fireTableDataChanged(); } public void useOffsets(boolean useOffsets) { if (this.useOffsets != useOffsets) { this.useOffsets = useOffsets; fireTableDataChanged(); } } /** a method that will get a data row from its index */ public DataRow getRowAt(int rowdex) { return ((DataRow) rows.get(rowdex)); } public int getRowCount() { return rows.size(); } public int getColumnCount() { int retval = columnNames.length; retval -= (maxMinVisible) ? 0 : 2; retval -= (thresholdVisible) ? 0 : 2; return retval; } public String getColumnName(int col) { if (col < 3 || maxMinVisible) { return columnNames[col]; } else { return columnNames[col + 2]; } } public Object getValueAt(int row, int col) { if (col != 0 && cleared) { return new String(); } if (row == -1) { return null; } DataRow dataRow = (DataRow) rows.get(row); if (dataRow == BLANK_ROW) { return null; } String[] nameSplit = dataRow.getName().split("/"); if (col > 2 && !maxMinVisible) { col += 2; } switch (col) { case 0: return (nameSplit[nameSplit.length - 1]); case 1: return dataRow.isCleared() ? null : new Double(dataRow.getData() - (useOffsets ? dataRow.getOffset() : 0)); case 2: return dataRow.getUnit(); case 3: return dataRow.isCleared() ? null : new Double(dataRow.getMinimum() - (useOffsets ? dataRow.getOffset() : 0)); case 4: return dataRow.isCleared() ? null : new Double(dataRow.getMaximum() - (useOffsets ? dataRow.getOffset() : 0)); case 5: return (dataRow.minThresh == Double.NEGATIVE_INFINITY) ? null : new Double(dataRow.minThresh); case 6: return (dataRow.maxThresh == Double.POSITIVE_INFINITY) ? null : new Double(dataRow.maxThresh); default: return null; } } /** * Called when the user updates a cell in the table. This is used to let * the user set the thresholds. * * @param o * the value entered by the user * @param row * the row of the cell * @param col * the column of the cell */ public void setValueAt(Object o, int row, int col) { DataRow dataRow = (DataRow) rows.get(row); if (dataRow == BLANK_ROW) { return; } if (o == null) { return; } // adjust the col for optional columns if (col > 2 && !maxMinVisible) { col += 2; } String value = o.toString().trim(); switch (col) { case 5: // "Min Thresh" try { double minThreshold; if (value.length() == 0) { minThreshold = Double.NEGATIVE_INFINITY; } else { minThreshold = Double.parseDouble(value); } if (minThreshold <= dataRow.maxThresh) { dataRow.minThresh = minThreshold; properties.setProperty("minThreshold_" + dataRow.getName(), Double.toString(minThreshold)); fireTableCellUpdated(row, col); } } catch (Throwable e) { } break; case 6: // "Max Thresh" try { double maxThreshold; if (value.length() == 0) { maxThreshold = Double.POSITIVE_INFINITY; } else { maxThreshold = Double.parseDouble(value); } if (maxThreshold >= dataRow.minThresh) { dataRow.maxThresh = maxThreshold; properties.setProperty("maxThreshold_" + dataRow.getName(), Double.toString(maxThreshold)); fireTableCellUpdated(row, col); } } catch (Throwable e) { } break; } } public boolean getMaxMinVisibile() { return maxMinVisible; } public boolean getThresholdVisible() { return thresholdVisible; } // getThresholdVisible () public void setMaxMinVisible(boolean maxMinVisible) { if (this.maxMinVisible != maxMinVisible) { this.maxMinVisible = maxMinVisible; fireTableStructureChanged(); } } public void setThresholdVisible(boolean thresholdVisible) { if (this.thresholdVisible != thresholdVisible) { this.thresholdVisible = thresholdVisible; fireTableStructureChanged(); } } // setThresholdVisible () } private class DataRow { String name; String unit; double min; double max; double minThresh; double maxThresh; double time; double value; double offset; boolean cleared; public DataRow(String name, String unit, double lowerThresh, double upperThresh) { this.name = name; this.unit = unit; this.minThresh = lowerThresh; this.maxThresh = upperThresh; clearData(); } public String getName() { return name; } public String getUnit() { return unit; } public double getData() { return value; } public void setData(double timestamp, double data, String unit) { time = timestamp; value = data; this.unit = unit; min = cleared ? data : Math.min(min, data); max = cleared ? data : Math.max(max, data); cleared = false; } public double getTime() { return time; } public double getMinimum() { return min; } public double getMaximum() { return max; } public double getOffset() { return offset; } public void setOffset(double offset) { this.offset = offset; } public void clearData() { cleared = true; } public boolean isCleared() { return cleared; } } // inner class DataRow private class DoubleTableCellRenderer extends DefaultTableCellRenderer { /** serialization version identifier */ private static final long serialVersionUID = 1835123361189568703L; private boolean showEngineeringFormat; private DecimalFormat decimalFormatter; private DecimalFormat engineeringFormatter; public DoubleTableCellRenderer() { this(false); } public DoubleTableCellRenderer(boolean showEngineeringFormat) { super(); this.showEngineeringFormat = showEngineeringFormat; setHorizontalAlignment(SwingConstants.RIGHT); decimalFormatter = new DecimalFormat("0.000000000"); engineeringFormatter = new DecimalFormat("##0.000000000E0"); } public void setValue(Object value) { if (value != null && value instanceof Number) { if (showEngineeringFormat) { setText(engineeringFormatter.format(value)); } else { setText(decimalFormatter.format(value)); } } else { super.setValue(value); } } public void setShowEngineeringFormat(boolean showEngineeringFormat) { this.showEngineeringFormat = showEngineeringFormat; } } // inner class DoubleTableCellRenderer /** * An inner class that renders the data value cell by coloring it when it is * outside or approaching the threshold intervals. */ private class DataTableCellRenderer extends DoubleTableCellRenderer { /** serialization version identifier */ private static final long serialVersionUID = -2910432979644162805L; public DataTableCellRenderer() { super(); } /** * a method that will color the data value cell based on it's proximity * to some threshold */ public Component getTableCellRendererComponent(JTable aTable, Object aNumberValue, boolean aIsSelected, boolean aHasFocus, int aRow, int aColumn) { Component renderer = super.getTableCellRendererComponent(aTable, aNumberValue, aIsSelected, aHasFocus, aRow, aColumn); if (aIsSelected) { return renderer; } else if (aNumberValue == null || !(aNumberValue instanceof Number)) { renderer.setBackground(Color.white); renderer.setForeground(Color.black); return renderer; } double numberValue = ((Number) aNumberValue).doubleValue(); DataRow dataRow = ((DataTableModel) aTable.getModel()).getRowAt(aRow); double minThresh = dataRow.minThresh; double maxThresh = dataRow.maxThresh; // if either max or min threshold not set to a value (default // infinity), show only critical thresholds (do not show warnings!) boolean criticalOnly = (minThresh == Double.NEGATIVE_INFINITY) || (maxThresh == Double.POSITIVE_INFINITY); if (criticalOnly) { if (numberValue < minThresh || numberValue > maxThresh) { renderer.setBackground(Color.red); renderer.setForeground(Color.white); } else { renderer.setBackground(Color.white); renderer.setForeground(Color.black); } return renderer; } double warningThresh = WARNING_PERCENTAGE * (maxThresh - minThresh); double criticalThresh = CRITICAL_PERCENTAGE * (maxThresh - minThresh); double warningMinThresh = minThresh + warningThresh; double criticalMinThresh = minThresh + criticalThresh; double warningMaxThresh = maxThresh - warningThresh; double criticalMaxThresh = maxThresh - criticalThresh; if (numberValue < criticalMinThresh || numberValue > criticalMaxThresh) { renderer.setBackground(Color.red); renderer.setForeground(Color.white); } else if (numberValue < warningMinThresh || numberValue > warningMaxThresh) { renderer.setBackground(Color.yellow); renderer.setForeground(Color.black); } else { renderer.setBackground(Color.white); renderer.setForeground(Color.black); } return renderer; } } // getTableCellRendererComponent () class CheckBoxGroup { boolean selected; List<AbstractButton> checkBoxes; public CheckBoxGroup() { this(false); } public CheckBoxGroup(boolean selected) { this.selected = selected; checkBoxes = new ArrayList<AbstractButton>(); } public void addCheckBox(AbstractButton checkBox) { checkBoxes.add(checkBox); checkBox.setSelected(selected); } public void removeCheckBox(AbstractButton checkBox) { checkBoxes.remove(checkBox); } public void setSelected(boolean selected) { this.selected = selected; for (AbstractButton checkBox : checkBoxes) { checkBox.setSelected(selected); } } public boolean isSelected() { return selected; } } } // class