Java tutorial
//============================================================================== // // Copyright (c) 2002- // Authors: // * Andrew Hinton <ug60axh@cs.bham.ac.uk> (University of Birmingham) // * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford, formerly University of Birmingham) // * Mark Kattenbelt <mark.kattenbelt@comlab.ox.ac.uk> (University of Oxford) // //------------------------------------------------------------------------------ // // This file is part of PRISM. // // PRISM is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // PRISM is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with PRISM; if not, write to the Free Software Foundation, // Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // //============================================================================== package userinterface.graph; import java.awt.BorderLayout; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.StringTokenizer; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.table.AbstractTableModel; import org.jfree.chart.ChartPanel; import org.jfree.data.general.Series; import org.jfree.data.general.SeriesChangeEvent; import org.jfree.data.general.SeriesChangeListener; import org.jfree.data.xy.XYDataItem; import org.jfree.data.xy.XYIntervalDataItem; import org.jfree.data.xy.XYIntervalSeries; import com.orsoncharts.data.xyz.XYZDataItem; import com.orsoncharts.data.xyz.XYZSeries; import userinterface.GUIPlugin; import userinterface.GUIPrism; public class SeriesEditorDialog extends JDialog { //ATTRIBUTES private Action okAction; private Action cancelAction; private GUIPrism gui; private java.util.List<SeriesEditor> editors; private boolean cancelled; // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JPanel allPanel; private javax.swing.JPanel bottomPanel; private javax.swing.JPanel buttonPanel; private javax.swing.JButton cancelButton; private javax.swing.JPanel editorPanel; private javax.swing.JButton okayButton; private javax.swing.JTabbedPane tabbedPane; private javax.swing.JToolBar toolBar; // End of variables declaration//GEN-END:variables private GUIPlugin plugin; public static void makeSeriesEditor(GUIPlugin plugin, JFrame parent, JPanel graph, java.util.List<SeriesKey> series) { if (graph instanceof Graph) { if (((Graph) graph).getXAxisSettings().isLogarithmic() || ((Graph) graph).getYAxisSettings().isLogarithmic()) { plugin.message( "One of your axes has a logarithmic scale. When a logarithmic scale is active we temporarily \n" + "hide negative and zero values. For this reason it is not safe to edit values when either of \n" + "your axes is logarithmic. Please select numerical axes and switch back later."); return; } } Object lock = null; if (graph instanceof Graph) lock = ((Graph) graph).getSeriesLock(); if (graph instanceof Histogram) lock = ((Histogram) graph).getSeriesLock(); // case when we have 3d graph and we don't need any synchronization if (lock == null) { SeriesEditorDialog editor = new SeriesEditorDialog(plugin, parent, graph, series); editor.setVisible(true); return; } synchronized (lock) { SeriesEditorDialog editor = new SeriesEditorDialog(plugin, parent, graph, series); editor.setVisible(true); // DOESN'T CONTINUE UNTILL DISPOSED } } /** Creates new form GUIConstantsPicker */ private SeriesEditorDialog(GUIPlugin plugin, JFrame parent, JPanel graph, java.util.List<SeriesKey> series) { super(parent, "Graph Series Editor", true); this.plugin = plugin; this.editors = new ArrayList<SeriesEditor>(); initComponents(); AbstractAction cut = new AbstractAction() { public void actionPerformed(ActionEvent e) { editors.get(tabbedPane.getSelectedIndex()).cut(); } }; cut.putValue(Action.LONG_DESCRIPTION, "Cut the current selection to the clipboard"); //exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); cut.putValue(Action.NAME, "Cut"); cut.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallCut.png")); //cut.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); AbstractAction copy = new AbstractAction() { public void actionPerformed(ActionEvent e) { editors.get(tabbedPane.getSelectedIndex()).copy(); } }; copy.putValue(Action.LONG_DESCRIPTION, "Copies the current selection to the clipboard"); //exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); copy.putValue(Action.NAME, "Copy"); copy.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallCopy.png")); //copy.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); AbstractAction paste = new AbstractAction() { public void actionPerformed(ActionEvent e) { editors.get(tabbedPane.getSelectedIndex()).paste(); } }; paste.putValue(Action.LONG_DESCRIPTION, "Pastes the clipboard to the current selection"); //exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); paste.putValue(Action.NAME, "Paste"); paste.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallPaste.png")); //paste.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); AbstractAction delete = new AbstractAction() { public void actionPerformed(ActionEvent e) { editors.get(tabbedPane.getSelectedIndex()).delete(); } }; delete.putValue(Action.LONG_DESCRIPTION, "Deletes the current"); //exitAction.putValue(Action.SHORT_DESCRIPTION, "Exit"); delete.putValue(Action.NAME, "Delete"); delete.putValue(Action.SMALL_ICON, GUIPrism.getIconFromImage("smallDelete.png")); for (SeriesKey key : series) { SeriesSettings settings = null; if (graph instanceof Graph) settings = ((Graph) graph).getGraphSeries(key); if (graph instanceof Histogram) settings = ((Histogram) graph).getGraphSeries(key); if (graph instanceof Graph3D) settings = ((Graph3D) graph).getSeriesSettings(); Object DataSeries = null; if (graph instanceof Graph) DataSeries = (PrismXYSeries) ((Graph) graph).getXYSeries(key); if (graph instanceof Histogram) DataSeries = ((Histogram) graph).getXYSeries(key); if (graph instanceof Graph3D) DataSeries = ((Graph3D) graph).getScatterSeries(); SeriesEditor editor = new SeriesEditor(graph, DataSeries, settings, cut, copy, paste, delete); editor.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); tabbedPane.addTab(settings.getSeriesHeading(), editor); editors.add(editor); } this.getRootPane().setDefaultButton(okayButton); toolBar.add(cut); toolBar.add(copy); toolBar.add(paste); toolBar.add(delete); this.add(toolBar, BorderLayout.NORTH); this.cancelled = false; super.setBounds(new Rectangle(550, 300)); setResizable(true); setLocationRelativeTo(getParent()); // centre } /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ // <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents private void initComponents() { allPanel = new javax.swing.JPanel(); bottomPanel = new javax.swing.JPanel(); buttonPanel = new javax.swing.JPanel(); okayButton = new javax.swing.JButton(); cancelButton = new javax.swing.JButton(); editorPanel = new javax.swing.JPanel(); tabbedPane = new javax.swing.JTabbedPane(); toolBar = new javax.swing.JToolBar(); setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); setTitle("Series Data Editor"); setAlwaysOnTop(true); setMinimumSize(new java.awt.Dimension(550, 350)); addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(java.awt.event.WindowEvent evt) { closeDialog(evt); } }); allPanel.setLayout(new java.awt.BorderLayout()); allPanel.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5)); bottomPanel.setLayout(new java.awt.BorderLayout()); buttonPanel.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT)); okayButton.setText("Okay"); okayButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { okayButtonActionPerformed(evt); } }); buttonPanel.add(okayButton); cancelButton.setText("Cancel"); cancelButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { cancelButtonActionPerformed(evt); } }); buttonPanel.add(cancelButton); bottomPanel.add(buttonPanel, java.awt.BorderLayout.EAST); allPanel.add(bottomPanel, java.awt.BorderLayout.SOUTH); editorPanel.setLayout(new java.awt.BorderLayout()); tabbedPane.setTabPlacement(javax.swing.JTabbedPane.BOTTOM); editorPanel.add(tabbedPane, java.awt.BorderLayout.CENTER); allPanel.add(editorPanel, java.awt.BorderLayout.CENTER); toolBar.setFloatable(false); allPanel.add(toolBar, java.awt.BorderLayout.NORTH); getContentPane().add(allPanel, java.awt.BorderLayout.CENTER); }// </editor-fold>//GEN-END:initComponents private void okayButtonActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_okayButtonActionPerformed {//GEN-HEADEREND:event_okayButtonActionPerformed dispose(); }//GEN-LAST:event_okayButtonActionPerformed private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_cancelButtonActionPerformed {//GEN-HEADEREND:event_cancelButtonActionPerformed dispose(); }//GEN-LAST:event_cancelButtonActionPerformed /** Closes the dialog */ private void closeDialog(java.awt.event.WindowEvent evt)//GEN-FIRST:event_closeDialog { setVisible(false); dispose(); }//GEN-LAST:event_closeDialog public boolean isCancelled() { return cancelled; } /** * This class represents the data of a single series. Multiple of these classes are combined * in one SeriesEditorDialog. */ private class SeriesEditor extends JPanel implements ActionListener { private int bufferSize = 30; private java.util.List<Double> xAxisBuffer; private java.util.List<Double> yAxisBuffer; private JPanel graph; private SeriesSettings settings; private Series xySeries; //for 3d graph private XYZSeries xyzSeries; private AbstractTableModel tableModel; private JTable table; private SeriesEditor(JPanel graph, Object series, SeriesSettings settings, Action cut, Action copy, Action paste, Action delete) { super.setLayout(new BorderLayout()); this.graph = graph; this.settings = settings; if (series instanceof Series) { this.xySeries = (Series) series; this.xyzSeries = null; } else if (series instanceof XYZSeries) { this.xyzSeries = (XYZSeries) series; this.xySeries = null; } this.xAxisBuffer = new ArrayList<Double>(bufferSize); this.yAxisBuffer = new ArrayList<Double>(bufferSize); for (int b = 0; b < bufferSize; b++) { xAxisBuffer.add(null); yAxisBuffer.add(null); } if (this.xySeries != null) { this.xySeries.addChangeListener(new SeriesChangeListener() { public void seriesChanged(SeriesChangeEvent event) { SeriesEditor.this.tableModel.fireTableStructureChanged(); } }); } this.tableModel = new AbstractTableModel() { public int getColumnCount() { if (graph instanceof Graph) return 2; else return 3; } public int getRowCount() { if (xySeries != null) return SeriesEditor.this.xySeries.getItemCount() + bufferSize; // the case for 3d graph else return SeriesEditor.this.xyzSeries.getItemCount() + bufferSize; } public boolean isCellEditable(int rowIndex, int columnIndex) { return graph instanceof Graph; } public Object getValueAt(int rowIndex, int columnIndex) { if (xySeries != null) { if (rowIndex >= SeriesEditor.this.xySeries.getItemCount()) { int bufferIndex = rowIndex - SeriesEditor.this.xySeries.getItemCount(); Double bufferValue = (columnIndex == 0) ? xAxisBuffer.get(bufferIndex) : yAxisBuffer.get(bufferIndex); return (bufferValue == null) ? "" : bufferValue; } } else if (xyzSeries != null) { if (rowIndex >= SeriesEditor.this.xyzSeries.getItemCount()) { int bufferIndex = rowIndex - SeriesEditor.this.xyzSeries.getItemCount(); Double bufferValue = (columnIndex == 0) ? xAxisBuffer.get(bufferIndex) : yAxisBuffer.get(bufferIndex); return (bufferValue == null) ? "" : bufferValue; } } if (graph instanceof Graph) { XYDataItem dataItem = ((PrismXYSeries) SeriesEditor.this.xySeries).getDataItem(rowIndex); if (columnIndex == 0) return dataItem.getX(); else return dataItem.getY(); } else if (graph instanceof Histogram) { XYIntervalDataItem dataItem = (XYIntervalDataItem) ((XYIntervalSeries) SeriesEditor.this.xySeries) .getDataItem(rowIndex); if (columnIndex == 0) return dataItem.getXHighValue(); else if (columnIndex == 1) return dataItem.getXLowValue(); else return dataItem.getYValue(); } //this is the case when we have the 3d graph else { if (columnIndex == 0) return SeriesEditor.this.xyzSeries.getXValue(rowIndex); else if (columnIndex == 1) return SeriesEditor.this.xyzSeries.getYValue(rowIndex); else return SeriesEditor.this.xyzSeries.getZValue(rowIndex); } } public String getColumnName(int column) { if (graph instanceof Graph) { if (column == 0) return ((Graph) SeriesEditor.this.graph).getXAxisSettings().getHeading(); else return ((Graph) SeriesEditor.this.graph).getYAxisSettings().getHeading(); } else if (graph instanceof Histogram) { if (column == 0) return "Min range"; else if (column == 1) return "Max range"; else return "Number of states"; } // case when we have the 3d graph else { if (column == 0) return "X"; else if (column == 1) return "Y"; else return "Z"; } } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (SeriesEditor.this.xySeries == null) { return; } Double value = Double.NaN; try { value = Double.parseDouble(aValue.toString()); } catch (NumberFormatException e) { // Remains NaN } if (aValue.toString().trim().equals("")) value = null; /* If not in current graph. */ if (rowIndex >= SeriesEditor.this.xySeries.getItemCount()) { int bufferIndex = rowIndex - SeriesEditor.this.xySeries.getItemCount(); // Set buffer if (columnIndex == 0) xAxisBuffer.set(bufferIndex, value); else yAxisBuffer.set(bufferIndex, value); Double otherBufferValue = (columnIndex == 0) ? yAxisBuffer.get(bufferIndex) : xAxisBuffer.get(bufferIndex); /* If row is filled in, then lets go! */ if (value != null && otherBufferValue != null) { if (columnIndex == 0 && Double.isNaN(value) || columnIndex == 1 && otherBufferValue.isNaN()) { // Cannot add yet! } else { clearBufferRow(bufferIndex); if (graph instanceof Graph) { if (columnIndex == 0) ((PrismXYSeries) SeriesEditor.this.xySeries).addOrUpdate(new Double(value), otherBufferValue); else ((PrismXYSeries) SeriesEditor.this.xySeries).addOrUpdate(otherBufferValue, new Double(value)); } } } } // Updating graph points... else { XYDataItem dataItem = ((PrismXYSeries) SeriesEditor.this.xySeries).getDataItem(rowIndex); // Null values are for in the buffer only. if (value == null) value = Double.NaN; // Updating point on x-axis if (columnIndex == 0) { if (Double.isNaN(value)) { Object[] options = { "Yes", "No" }; if (SeriesEditorDialog.this.plugin.question("Invalid value", "You have entered an invalid value on the x-axis. This \n" + "will result in deleting the datapoint. Do you want to continue?", options, 1) == 1) return; } if (graph instanceof Graph) { Double yValue = ((PrismXYSeries) SeriesEditor.this.xySeries).getY(rowIndex) .doubleValue(); ((PrismXYSeries) SeriesEditor.this.xySeries).remove(rowIndex); ((PrismXYSeries) SeriesEditor.this.xySeries).addOrUpdate(new Double(value), yValue); } } else { // Updating point on y-axis if (graph instanceof Graph) ((PrismXYSeries) SeriesEditor.this.xySeries).updateByIndex(rowIndex, value); } //super.setValueAt(aValue, rowIndex, columnIndex); } fireTableStructureChanged(); } }; this.table = new JTable(tableModel); //Next 3 lines thanks to //http://forum.java.sun.com/thread.jsp?thread=529548&forum=57&message=2546795 //This is to disable to automatic Ctrl-C put onto JTables, as it does //not provide the correct functionality. InputMap im = this.table.getInputMap(); ActionMap am = this.table.getActionMap(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "cut"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "copy"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "paste"); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete"); am.put("cut", cut); am.put("copy", copy); am.put("paste", paste); am.put("delete", delete); this.table.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); this.table.setRowSelectionAllowed(true); //this.table.setColumnSelectionAllowed(true); JScrollPane scroll = new JScrollPane(); scroll.setViewportView(table); add(scroll, BorderLayout.CENTER); } public void addBufferRow() { xAxisBuffer.add(null); yAxisBuffer.add(null); bufferSize++; tableModel.fireTableStructureChanged(); } public boolean isBufferRow(int rowIndex) { if (xySeries != null) return (rowIndex >= xySeries.getItemCount()); //3d graph case else return (rowIndex >= xyzSeries.getItemCount()); } public boolean isClearBufferRow(int rowIndex) { int bufferRowIndex = -1; if (xySeries != null) { bufferRowIndex = rowIndex - xySeries.getItemCount(); } else if (xyzSeries != null) { bufferRowIndex = rowIndex - xyzSeries.getItemCount(); } return isBufferRow(rowIndex) && xAxisBuffer.get(bufferRowIndex) == null && yAxisBuffer.get(bufferRowIndex) == null; } public void clearBufferRow(int rowIndex) { xAxisBuffer.set(rowIndex, null); yAxisBuffer.set(rowIndex, null); tableModel.fireTableStructureChanged(); } /** * Returns row index in terms of table. */ public int firstClearBufferIndex() { int itemCount = -1; if (xySeries != null) { itemCount = xySeries.getItemCount(); } else if (xyzSeries != null) { itemCount = xyzSeries.getItemCount(); } for (int b = 0; b < bufferSize; b++) { if (isClearBufferRow(b + itemCount)) return b + itemCount; } /* No clear buffer rows, lets add new row. */ addBufferRow(); return bufferSize + itemCount - 1; } public void cut() { copy(); delete(); } public void copy() { if (xySeries == null) { return; } int[] rows = table.getSelectedRows(); StringBuffer clippy = new StringBuffer(); for (int i = 0; i < rows.length; i++) { int row = rows[i]; if (row < xySeries.getItemCount()) { if (graph instanceof Graph) { XYDataItem item = ((PrismXYSeries) xySeries).getDataItem(row); clippy.append(item.getX() + "\t" + item.getY() + "\n"); } } else { int bufferRow = row - xySeries.getItemCount(); String x = (xAxisBuffer.get(bufferRow) == null) ? "" : xAxisBuffer.get(bufferRow).toString(); String y = (yAxisBuffer.get(bufferRow) == null) ? "" : yAxisBuffer.get(bufferRow).toString(); clippy.append(x + "\t" + y + "\n"); } } Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection gs = new StringSelection(clippy.toString()); clipboard.setContents(gs, null); } public void paste() { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable contents = clipboard.getContents(null); int[] sel = table.getSelectedRows(); int row = -1; /* If first selected is a buffer row, then paste from there onwards. */ if (sel.length > 0 && isBufferRow(sel[0])) row = sel[0]; try { if (contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { String str = (String) contents.getTransferData(DataFlavor.stringFlavor); StringTokenizer rows = new StringTokenizer(str, "\n"); while (rows.hasMoreTokens()) { String rowStr = rows.nextToken(); int tabIndex = rowStr.indexOf("\t"); if (tabIndex != -1) { String xValue = rowStr.substring(0, tabIndex).trim(); String yValue = rowStr.substring(tabIndex, rowStr.length()).trim(); int bufferRow = (row == -1) ? firstClearBufferIndex() : row; tableModel.setValueAt("", bufferRow, 0); tableModel.setValueAt("", bufferRow, 1); tableModel.setValueAt(xValue, bufferRow, 0); tableModel.setValueAt(yValue, bufferRow, 1); if (row != -1) { row++; if (row >= tableModel.getRowCount()) addBufferRow(); } } } } } catch (Exception e) { } } public void delete() { if (xySeries == null) { return; } int[] selectedRows = table.getSelectedRows(); for (int row = selectedRows.length - 1; row >= 0; row--) { int rowIndex = selectedRows[row]; if (rowIndex >= xySeries.getItemCount()) { clearBufferRow(rowIndex - xySeries.getItemCount()); } else { if (graph instanceof Graph) ((PrismXYSeries) xySeries).remove(rowIndex); else if (graph instanceof Histogram) ((XYIntervalSeries) xySeries).remove(rowIndex); } } } public SeriesSettings getSettings() { return settings; } public void setSettings(SeriesSettings settings) { this.settings = settings; } public Series getXySeries() { return xySeries; } public XYZSeries getXYZSeries() { return xyzSeries; } public void setXySeries(PrismXYSeries xySeries) { this.xySeries = xySeries; } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("cut")) cut(); else if (e.getActionCommand().equals("copy")) copy(); else if (e.getActionCommand().equals("paste")) paste(); else if (e.getActionCommand().equals("delete")) delete(); } } }