/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.tools; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collection; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.AbstractCellEditor; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.MouseInputAdapter; import javax.swing.event.TableModelEvent; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableModel; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotUndoException; import org.opensourcephysics.controls.OSPLog; import org.opensourcephysics.display.DataFunction; import org.opensourcephysics.display.DataTable; import org.opensourcephysics.display.Dataset; import org.opensourcephysics.display.DatasetManager; import org.opensourcephysics.display.DrawableTextLine; import org.opensourcephysics.display.DrawingPanel; import org.opensourcephysics.display.HighlightableDataset; import org.opensourcephysics.display.OSPRuntime; import org.opensourcephysics.display.TeXParser; import org.opensourcephysics.display.TextLine; /** * This is a DataTable that displays DataColumns * and constructs HighlightableDatasets for a plot. * * @author Douglas Brown * @version 1.0 */ public class DataToolTable extends DataTable { // static fields and constants protected final static int RENAME_COLUMN_EDIT = 0; protected final static int INSERT_COLUMN_EDIT = 1; protected final static int DELETE_COLUMN_EDIT = 2; protected final static int INSERT_CELLS_EDIT = 3; protected final static int DELETE_CELLS_EDIT = 4; protected final static int REPLACE_CELLS_EDIT = 5; protected final static int INSERT_ROWS_EDIT = 6; protected final static int DELETE_ROWS_EDIT = 7; protected static String[] editTypes = {"rename column", "insert column", //$NON-NLS-1$ //$NON-NLS-2$ "delete column", "insert cells", "delete cells", "replace cells", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ "insert rows", "delete rows"}; //$NON-NLS-1$ //$NON-NLS-2$ protected static Color xAxisColor = new Color(255, 255, 153); // yellow protected static Color yAxisColor = new Color(204, 255, 204); // light green // instance fields DataToolTab dataToolTab; // tab that displays this table DatasetManager dataManager; // manages datasets for table WorkingDataset workingData; // first two table columns in x-y order HashMap<String, WorkingDataset> workingMap = new HashMap<String, WorkingDataset>(); // maps column name to working dataset HighlightableDataset selectedData = new HighlightableDataset(); // selected rows of working data HeaderRenderer headerRenderer; LabelRenderer labelRenderer = new LabelRenderer(); DataCellRenderer dataRenderer = new DataCellRenderer(); DataEditor editor = new DataEditor(); TreeSet<Integer> selectedRows = new TreeSet<Integer>(); // selected model rows--used when sorting TreeSet<Integer> selectedColumns = new TreeSet<Integer>(); // selected model columns--used when selecting rows JPopupMenu popup = new JPopupMenu(); JMenuItem renameColumnItem, copyColumnsItem, cutColumnsItem, pasteColumnsItem, cloneColumnsItem, numberFormatItem; JMenuItem insertRowItem, pasteRowsItem, copyRowsItem, cutRowsItem; JMenuItem insertCellsItem, deleteCellsItem, copyCellsItem, cutCellsItem, pasteInsertCellsItem, pasteCellsItem; JMenuItem addEndRowItem, trimRowsItem; JMenuItem selectAllItem, selectNoneItem, clearContentsItem; Action clearCellsAction, pasteCellsAction, pasteInsertCellsAction, cantPasteCellsAction, cantPasteRowsAction, getPasteDataAction; MouseAdapter tableMouseListener; Color selectedBG, selectedFG, unselectedBG, selectedHeaderFG, selectedHeaderBG, rowBG; int focusRow, focusCol, mouseRow, mouseCol; int leadCol = 0, leadRow = 0, prevSortedColumn; // used for shift-click selections int pasteW, pasteH; HashMap<String, double[]> pasteValues = new HashMap<String, double[]>(); DatasetManager pasteData = null; HashMap<Integer, Integer> workingRows = new HashMap<Integer, Integer>(); // maps working row to model row /** * Constructs a DataToolTable for the specified DataTooltab. * * @param tab */ public DataToolTable(DataToolTab tab) { super(new DataToolTableModel(tab)); dataToolTab = tab; dataManager = tab.dataManager; add(dataManager); setRowNumberVisible(true); setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); headerRenderer = new HeaderRenderer(getTableHeader().getDefaultRenderer()); getTableHeader().setDefaultRenderer(headerRenderer); // selection listener for table ListSelectionModel selectionModel = getSelectionModel(); selectionModel.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (e.getFirstIndex()==-1) { return; } selectedRows.clear(); int[] rows = getSelectedRows(); // selected view rows for(int i = 0; i<rows.length; i++) { int modelRow = getModelRow(rows[i]); selectedRows.add(modelRow); } if (!e.getValueIsAdjusting()) { int labelCol = convertColumnIndexToView(0); addColumnSelectionInterval(labelCol, labelCol); dataToolTab.setSelectedData(getSelectedData()); } } }); // selection listener for column header selectionModel = getColumnModel().getSelectionModel(); selectionModel.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { getTableHeader().repaint(); dataToolTab.refreshPlot(); } }); // create actions clearCellsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { // clear selected cells by replacing with NaN HashMap<String, double[]> values = new HashMap<String, double[]>(); int[] rows = getSelectedModelRows(); Iterator<String> it = getSelectedColumnNames().iterator(); while(it.hasNext()) { values.put(it.next(), null); } HashMap<String, double[]> prev = replaceCells(rows, values); // post edit: target is rows, value is HashMap[] {undo, redo} TableEdit edit = new TableEdit(REPLACE_CELLS_EDIT, null, rows, new HashMap[] {prev, values}); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }; pasteCellsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { int[] rows = getSelectedModelRows(); if(!pasteValues.isEmpty()&&((rows.length==1)||(pasteH==rows.length))) { int[] pasteRows = new int[pasteH]; if(pasteH==rows.length) { pasteRows = rows; } else { pasteRows[0] = rows[0]; int vRow = getViewRow(rows[0]); for(int i = 1; i<pasteH; i++) { while(vRow+i>=getRowCount()) { int[] row = new int[] {getRowCount()}; insertRows(row, null); } pasteRows[i] = getModelRow(vRow+i); } } HashMap<String, double[]> prev = replaceCells(pasteRows, pasteValues); // post edit: target is rows, value is HashMap[] {undo, redo} TableEdit edit = new TableEdit(REPLACE_CELLS_EDIT, null, pasteRows, new HashMap[] {prev, pasteValues}); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } else { cantPasteCellsAction.actionPerformed(e); } } }; pasteInsertCellsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { int[] rows = getSelectedModelRows(); if(!pasteValues.isEmpty()&&((rows.length==1)||(pasteH==rows.length))) { int[] pasteRows = new int[pasteH]; if(pasteH==rows.length) { pasteRows = rows; } else { pasteRows[0] = rows[0]; int vRow = getViewRow(rows[0]); for(int i = 1; i<pasteH; i++) { while(vRow+i>=getRowCount()) { int[] row = new int[] {getRowCount()}; insertRows(row, null); } pasteRows[i] = getModelRow(vRow+i); } } insertCells(pasteRows, pasteValues); // post edit: target is rows, value is HashMap TableEdit edit = new TableEdit(INSERT_CELLS_EDIT, null, pasteRows, pasteValues); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } else { cantPasteCellsAction.actionPerformed(e); } } }; cantPasteCellsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(dataToolTab, ToolsRes.getString("DataToolTable.Dialog.CantPasteCells.Message1") //$NON-NLS-1$ +" "+pasteW+" x "+pasteH+"\n" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ +ToolsRes.getString("DataToolTable.Dialog.CantPasteCells.Message2"), //$NON-NLS-1$ ToolsRes.getString("DataToolTable.Dialog.CantPaste.Title"), //$NON-NLS-1$ JOptionPane.WARNING_MESSAGE); } }; cantPasteRowsAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(dataToolTab, ToolsRes.getString("DataToolTable.Dialog.CantPasteRows.Message1") //$NON-NLS-1$ +" "+pasteH+"\n" //$NON-NLS-1$ //$NON-NLS-2$ +ToolsRes.getString("DataToolTable.Dialog.CantPasteRows.Message2"), //$NON-NLS-1$ ToolsRes.getString("DataToolTable.Dialog.CantPaste.Title"), //$NON-NLS-1$ JOptionPane.WARNING_MESSAGE); } }; getPasteDataAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { pasteValues.clear(); pasteData = null; // put clipboard data into pasteValues map String dataString = DataTool.paste(); ArrayList<String> colNames = getSelectedColumnNames(); if(dataString!=null) { pasteData = DataTool.parseData(dataString, null); if(pasteData!=null) { pasteW = pasteData.getDatasets().size(); if((pasteW>0)&&(pasteW==colNames.size())) { pasteH = pasteData.getDataset(0).getXPoints().length; if(pasteH>0) { for(int i = 0; i<pasteW; i++) { double[] vals = pasteData.getDataset(i).getYPoints(); pasteValues.put(colNames.get(i), vals); } } } } } } }; // mouse motion listener for table header getTableHeader().addMouseMotionListener(new MouseInputAdapter() { public void mouseMoved(MouseEvent e) { int n = getTableHeader().columnAtPoint(e.getPoint()); n = convertColumnIndexToModel(n); if(n==0) { getTableHeader().setToolTipText(ToolsRes.getString("DataToolTable.Header.Deselect.Tooltip")); //$NON-NLS-1$ } else { getTableHeader().setToolTipText(ToolsRes.getString("DataToolTable.Header.Tooltip")); //$NON-NLS-1$ } } }); // mouse listener for table header getTableHeader().addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (getRowCount()==0) return; final java.awt.Point mousePt = e.getPoint(); final int col = columnAtPoint(mousePt); if(col==-1) { return; } int labelCol = convertColumnIndexToView(0); // save selected columns and rows ArrayList<String> cols = getSelectedColumnNames(); // right-click: set lead column and show popup menu if(OSPRuntime.isPopupTrigger(e)) { if(col==labelCol) { return; } String colName = getColumnName(col); if(!cols.contains(colName)) { setColumnSelectionInterval(col, col); leadCol = col; } setRowSelectionInterval(0, getRowCount()-1); popup.removeAll(); String text; // get newly selected columns cols = getSelectedColumnNames(); // rename column item if((cols.size()==1)&&dataToolTab.isUserEditable()) { int index = convertColumnIndexToModel(col)-1; final Dataset data = dataManager.getDataset(index); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.RenameColumn"); //$NON-NLS-1$ renameColumnItem = new JMenuItem(text); renameColumnItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(data instanceof DataFunction) { showDataBuilder(); return; } String prevName = data.getYColumnName(); Object input = JOptionPane.showInputDialog(dataToolTab, ToolsRes.getString("DataToolTable.Dialog.NameColumn.Message"), //$NON-NLS-1$ ToolsRes.getString("DataToolTable.Dialog.NameColumn.Title"), //$NON-NLS-1$ JOptionPane.QUESTION_MESSAGE, null, null, prevName); if((input==null)||input.equals("")) { //$NON-NLS-1$ return; } String newName = dataToolTab.getUniqueYColumnName(data, input.toString(), true); if(newName==null) { return; } // remove any characters after a closing brace int n = newName.indexOf("}"); //$NON-NLS-1$ if (n==0) return; if (n>-1) { newName = newName.substring(0, n+1); } renameColumn(prevName, newName); // post edit: target is null, value is previous name TableEdit edit = new TableEdit(RENAME_COLUMN_EDIT, newName, null, prevName); dataToolTab.undoSupport.postEdit(edit); } }); popup.add(renameColumnItem); popup.addSeparator(); } // copy column item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CopyColumns"); //$NON-NLS-1$ copyColumnsItem = new JMenuItem(text); copyColumnsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataToolTab.copyTableDataToClipboard(); } }); popup.add(copyColumnsItem); // cut columns item: only if all selected columns are deletable boolean addCutItem = true; for(String name : cols) { if(!dataToolTab.isDeletable(getDataset(name))) { addCutItem = false; } } if(addCutItem) { text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CutColumns"); //$NON-NLS-1$ cutColumnsItem = new JMenuItem(text); cutColumnsItem.setActionCommand(String.valueOf(col)); cutColumnsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copyColumnsItem.doClick(); deleteSelectedColumns(); // also posts undoable edits } }); popup.add(cutColumnsItem); } // paste columns item if((dataToolTab!=null)&&(dataToolTab.dataTool!=null)&&dataToolTab.dataTool.hasPastableData()&&dataToolTab.dataTool.hasPastableColumns(dataToolTab)) { text = ToolsRes.getString("DataToolTable.Popup.MenuItem.PasteColumns"); //$NON-NLS-1$ pasteColumnsItem = new JMenuItem(text); pasteColumnsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataToolTab.dataTool.pasteColumnsItem.doClick(0); } }); popup.add(pasteColumnsItem); } // clone column item popup.addSeparator(); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CloneColumns"); //$NON-NLS-1$ cloneColumnsItem = new JMenuItem(text); cloneColumnsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ArrayList<String> colNames = getSelectedColumnNames(); for(int i = 0; i<colNames.size(); i++) { Dataset data = getDataset(colNames.get(i)); if(data==null) { continue; } Dataset clone = DataTool.copyDataset(data, null, false); double[] x = data.getXPoints(); double[] y = data.getYPoints(); clone.append(x, y); String name = data.getYColumnName(); String postfix = "_"+ToolsRes.getString("DataTool.Clone.Subscript"); //$NON-NLS-1$ //$NON-NLS-2$ int n = name.indexOf(postfix); if(n>-1) { name = name.substring(0, n); } name = name+postfix; name = dataToolTab.getUniqueYColumnName(clone, name, false); clone.setXYColumnNames(data.getXColumnName(), name); ArrayList<DataColumn> loadedColumns = dataToolTab.loadData(clone, false); if(!loadedColumns.isEmpty()) { for(DataColumn next : loadedColumns) { next.deletable = true; } } } } }); popup.add(cloneColumnsItem); // number format item popup.addSeparator(); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.NumberFormat"); //$NON-NLS-1$ numberFormatItem = new JMenuItem(text); numberFormatItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // get list of all column names int colCount = getColumnCount(); String[] names = new String[colCount-1]; int labelCol = convertColumnIndexToView(0); int index = 0; for (int i = 0; i < colCount; i++) { if (i==labelCol) continue; String name = getColumnName(i); names[index] = name; index++; } ArrayList<String> selected = getSelectedColumnNames(); String[] selectedNames = new String[selected.size()]; for (int i = 0; i < selectedNames.length; i++) { selectedNames[i] = selected.get(i); } NumberFormatDialog dialog = getFormatDialog(names, selectedNames); dialog.setVisible(true); dataToolTab.refreshPlot(); } }); popup.add(numberFormatItem); FontSizer.setFonts(popup, FontSizer.getLevel()); popup.show(getTableHeader(), e.getX(), e.getY()+8); } // double-click: select column and all rows (or select table if label column) else if(e.getClickCount()==2) { if(col==labelCol) { selectAllCells(); } else { setRowSelectionInterval(0, getRowCount()-1); // all rows setColumnSelectionInterval(col, col); leadCol = col; } // sort by row number sort(0); } // single click label column: refresh selected rows else if (col==labelCol && getSortedColumn()==col) { if (col!=prevSortedColumn) { int[] rows = getSelectedModelRows(); setSelectedModelRows(rows); prevSortedColumn = col; } } // control-click: add/remove columns to selection else if (e.isControlDown()) { if(col!=labelCol && isColumnSelected(col)) { removeColumnSelectionInterval(col, col); } else { if (!selectedRows.isEmpty()) addColumnSelectionInterval(col, col); if(getSelectedColumns().length==1) { leadCol = col; } } } // shift-click: extend selection else if(e.isShiftDown() && !selectedRows.isEmpty()) { if(leadCol<getColumnCount()) { setColumnSelectionInterval(col, leadCol); } } // single click: refresh selected rows else { if (col!=prevSortedColumn) { int[] rows = getSelectedModelRows(); setSelectedModelRows(rows); prevSortedColumn = col; } } getSelectedData(); // save selected columns addColumnSelectionInterval(labelCol, labelCol); selectedColumns.clear(); int[] selected = getSelectedColumns(); // selected view columns for(int i = 0; i<selected.length; i++) { if (selected[i]==labelCol) { continue; } int modelCol = convertColumnIndexToModel(selected[i]); selectedColumns.add(modelCol); } if (selectedColumns.isEmpty()) { clearSelection(); } } }); // mouse motion listener for table addMouseMotionListener(new MouseInputAdapter() { public void mouseMoved(MouseEvent e) { if(!popup.isVisible()) { int row = rowAtPoint(e.getPoint()); int col = columnAtPoint(e.getPoint()); int labelCol = convertColumnIndexToView(0); mouseRow = row; mouseCol = col; dataRenderer.showFocus = (col==labelCol); repaint(); if(col==labelCol) { dataRenderer.showFocus = true; setToolTipText(ToolsRes.getString("DataToolTable.Deselect.Tooltip")); //$NON-NLS-1$ } else { Object obj = getValueAt(row, col); String name = getColumnName(col); setToolTipText(name+" = "+obj); //$NON-NLS-1$ } } requestFocusInWindow(); } public void mouseDragged(MouseEvent e) { int col = columnAtPoint(e.getPoint()); int row = rowAtPoint(e.getPoint()); mouseRow = row; mouseCol = col; int labelCol = convertColumnIndexToView(0); if(col==labelCol) { if(leadRow<getRowCount()) { setRowSelectionInterval(leadRow, row); } setColumnSelectionInterval(getColumnCount()-1, 0); } dataRenderer.showFocus = false; // update selected data in curve fitter and plot dataToolTab.setSelectedData(getSelectedData()); dataToolTab.plot.repaint(); } }); // mouse listener for table tableMouseListener = new MouseAdapter() { public void mouseExited(MouseEvent e) { if(!popup.isVisible()) { mouseRow = -1; dataRenderer.showFocus = true; repaint(); } } public void mousePressed(MouseEvent e) { final int col = columnAtPoint(e.getPoint()); final int row = rowAtPoint(e.getPoint()); int labelCol = convertColumnIndexToView(0); // right-click: show popup menu if(OSPRuntime.isPopupTrigger(e)) { editor.stopCellEditing(); // select appropriate cells if(col==labelCol) { if(!isRowSelected(row)) { setRowSelectionInterval(row, row); } setColumnSelectionInterval(0, getColumnCount()-1); } else if(!isCellSelected(row, col)) { setRowSelectionInterval(row, row); setColumnSelectionInterval(col, col); leadCol = col; leadRow = row; } repaint(); getPasteDataAction.actionPerformed(null); final int[] rows = getSelectedModelRows(); // determine if selection is all empty cells boolean isEmptyCells = true; int[] selectedRows = getSelectedRows(); ArrayList<String> selectedColumns = getSelectedColumnNames(); for(int i = 0; i<selectedRows.length; i++) { if(!isEmptyCells(selectedRows[i], selectedColumns)) { isEmptyCells = false; break; } } popup.removeAll(); String text; // data cell clicked: show cells popup if(col!=labelCol) { int index = convertColumnIndexToModel(col)-1; final Dataset data = dataManager.getDataset(index); mouseRow = row; mouseCol = col; repaint(); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.SelectAll"); //$NON-NLS-1$ selectAllItem = new JMenuItem(text); selectAllItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { selectAllCells(); } }); popup.add(selectAllItem); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.SelectNone"); //$NON-NLS-1$ selectNoneItem = new JMenuItem(text); selectNoneItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { clearSelection(); } }); popup.add(selectNoneItem); popup.addSeparator(); if(dataToolTab.isUserEditable() && !(data instanceof DataFunction)) { // insert cells item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.InsertCells"); //$NON-NLS-1$ insertCellsItem = new JMenuItem(text); insertCellsItem.setActionCommand(String.valueOf(col)); insertCellsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { HashMap<String, double[]> emptyRow = new HashMap<String, double[]>(); Iterator<String> it = getSelectedColumnNames().iterator(); while(it.hasNext()) { emptyRow.put(it.next(), null); } insertCells(rows, emptyRow); // post edit: target is rows, value is HashMap TableEdit edit = new TableEdit(INSERT_CELLS_EDIT, null, rows, emptyRow); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }); popup.add(insertCellsItem); // paste insert cells item if (pasteData!=null) { text = ToolsRes.getString("DataToolTable.Popup.MenuItem.PasteInsertCells"); //$NON-NLS-1$ pasteInsertCellsItem = new JMenuItem(text); pasteInsertCellsItem.setActionCommand(String.valueOf(col)); pasteInsertCellsItem.addActionListener(pasteInsertCellsAction); popup.add(pasteInsertCellsItem); } // delete cells item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.DeleteCells"); //$NON-NLS-1$ deleteCellsItem = new JMenuItem(text); deleteCellsItem.setActionCommand(String.valueOf(col)); deleteCellsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Iterator<String> it = getSelectedColumnNames().iterator(); while(it.hasNext()) { pasteValues.put(it.next(), null); } HashMap<String, double[]> prev = deleteCells(rows, pasteValues); // post edit: target is rows, value is HashMap TableEdit edit = new TableEdit(DELETE_CELLS_EDIT, null, rows, prev); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }); popup.add(deleteCellsItem); } if(!isEmptyCells||(pasteData!=null)) { if (popup.getComponentCount()>0 && !dataToolTab.originShiftEnabled) { popup.addSeparator(); } if(!isEmptyCells) { // copy cells item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CopyCells"); //$NON-NLS-1$ copyCellsItem = new JMenuItem(text); copyCellsItem.setActionCommand(String.valueOf(col)); copyCellsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dataToolTab.copyTableDataToClipboard(); } }); popup.add(copyCellsItem); if (dataToolTab.isUserEditable() && !(data instanceof DataFunction)) { // cut cells item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CutCells"); //$NON-NLS-1$ cutCellsItem = new JMenuItem(text); cutCellsItem.setActionCommand(String.valueOf(col)); cutCellsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copyCellsItem.doClick(); clearCellsAction.actionPerformed(e); } }); popup.add(cutCellsItem); text = ToolsRes.getString("DataToolTable.Popup.MenuItem.DeleteContents"); //$NON-NLS-1$ clearContentsItem = new JMenuItem(text); clearContentsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { clearCellsAction.actionPerformed(null); } }); popup.add(clearContentsItem); } } if (dataToolTab.isUserEditable() && pasteData!=null) { // paste cells item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.PasteCells"); //$NON-NLS-1$ pasteCellsItem = new JMenuItem(text); pasteCellsItem.setActionCommand(String.valueOf(col)); pasteCellsItem.addActionListener(pasteCellsAction); popup.add(pasteCellsItem); } } } // label cell clicked: set lead row and show row popup else { leadRow = row; if(dataToolTab.isUserEditable()) { // insert row item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.InsertRows"); //$NON-NLS-1$ insertRowItem = new JMenuItem(text); insertRowItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { HashMap<String, double[]> prev = insertRows(rows, null); // post edit: target is rows, value is map TableEdit edit = new TableEdit(INSERT_ROWS_EDIT, null, rows, prev); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }); popup.add(insertRowItem); // determine if clipboard contains row data boolean hasRows = !pasteValues.isEmpty(); if(hasRows) { Iterator<String> it = pasteValues.keySet().iterator(); while(it.hasNext()) { String next = it.next(); hasRows = hasRows&&(pasteData.getDatasetIndex(next)>-1); } } // paste rows item if(hasRows) { text = ToolsRes.getString("DataToolTable.Popup.MenuItem.PasteInsertRows"); //$NON-NLS-1$ pasteRowsItem = new JMenuItem(text); pasteRowsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if((rows.length!=1)&&(pasteH!=rows.length)) { cantPasteRowsAction.actionPerformed(e); return; } int[] pasteRows = new int[pasteH]; if(pasteH==rows.length) { pasteRows = rows; } else if(rows.length==1) { pasteRows[0] = rows[0]; for(int i = 1; i<pasteH; i++) { pasteRows[i] = rows[0]+i; } } insertRows(pasteRows, pasteValues); // post edit: target is rows, value is map TableEdit edit = new TableEdit(INSERT_ROWS_EDIT, null, pasteRows, pasteValues); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }); popup.add(pasteRowsItem); } popup.addSeparator(); } // copy row item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CopyRows"); //$NON-NLS-1$ copyRowsItem = new JMenuItem(text); copyRowsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { OSPLog.finest("copying rows"); //$NON-NLS-1$ String data = dataToolTab.getSelectedTableData(); DataTool.copy(data); } }); popup.add(copyRowsItem); if(dataToolTab.isUserEditable()) { // cut row item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.CutRows"); //$NON-NLS-1$ cutRowsItem = new JMenuItem(text); cutRowsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { copyRowsItem.doClick(); int[] rows = getSelectedModelRows(); HashMap<String, double[]> removed = deleteRows(rows); // post edit: target is rows, value is map TableEdit edit = new TableEdit(DELETE_ROWS_EDIT, null, rows, removed); dataToolTab.undoSupport.postEdit(edit); refreshUndoItems(); } }); popup.add(cutRowsItem); popup.addSeparator(); // addEndRow item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.AddEndRow"); //$NON-NLS-1$ addEndRowItem = new JMenuItem(text); addEndRowItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { insertRows(new int[] {getRowCount()}, null); } }); popup.add(addEndRowItem); if(isEmptyRow(getRowCount()-1)) { // trimRows item text = ToolsRes.getString("DataToolTable.Popup.MenuItem.TrimRows"); //$NON-NLS-1$ trimRowsItem = new JMenuItem(text); trimRowsItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { trimEmptyRows(0); } }); popup.add(trimRowsItem); } } } FontSizer.setFonts(popup, FontSizer.getLevel()); popup.show(DataToolTable.this, e.getX(), e.getY()+8); return; } // single click: show focus dataRenderer.showFocus = true; // label column clicked: clear selection or select row if(col==labelCol) { if(e.getClickCount()==2) { leadRow = row; setRowSelectionInterval(row, row); setColumnSelectionInterval(0, getColumnCount()-1); } // shift-click: extend row selection else if(e.isShiftDown()&&(leadRow<getRowCount())) { setRowSelectionInterval(leadRow, row); for (int i : selectedColumns) { int n = convertColumnIndexToView(i); addColumnSelectionInterval(n, n); } } // control-click: select/deselect rows else if(e.isControlDown()||e.isShiftDown()) { // leadRow = row; // if(getSelectedRows().length==0) { // clearSelection(); // } else { // setColumnSelectionInterval(0, getColumnCount()-1); // } } // single click: clear selection else { clearSelection(); leadRow = 0; leadCol = 1; } } else if(!e.isControlDown()&&!e.isShiftDown()) { leadRow = row; leadCol = col; } getSelectedData(); dataToolTab.plot.repaint(); // save selected columns addColumnSelectionInterval(labelCol, labelCol); selectedColumns.clear(); int[] selected = getSelectedColumns(); // selected view columns for(int i = 0; i<selected.length; i++) { if (selected[i]==labelCol) { continue; } int modelCol = convertColumnIndexToModel(selected[i]); selectedColumns.add(modelCol); } if (selectedColumns.isEmpty() || selectedRows.isEmpty()) { clearSelection(); } } }; addMouseListener(tableMouseListener); // override default enter action InputMap im = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); Action enterAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { // start editing focused cell editCellAt(focusRow, focusCol, e); editor.field.requestFocus(); } }; KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0); getActionMap().put(im.get(enter), enterAction); // override default copy action Action copyAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { dataToolTab.copyTableDataToClipboard(); } }; int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); KeyStroke copy = KeyStroke.getKeyStroke(KeyEvent.VK_C, mask); getActionMap().put(im.get(copy), copyAction); // override default paste action Action pasteAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { getPasteDataAction.actionPerformed(e); pasteCellsAction.actionPerformed(e); } }; KeyStroke paste = KeyStroke.getKeyStroke(KeyEvent.VK_V, mask); getActionMap().put(im.get(paste), pasteAction); // associate clear cells action with delete key KeyStroke delete = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0); im.put(delete, clearCellsAction); getActionMap().put(im.get(delete), clearCellsAction); } /** * Gets the working data for a specified column name. * The working y-data is the named table column * The working x-data is the x (yellow) table column * * @param colName the name of the data column * @return the working dataset */ protected WorkingDataset getWorkingData(String colName) { if (colName==null) { return null; } // find or create working data WorkingDataset working = workingMap.get(colName); if (working==null && dataToolTab.originShiftEnabled) { working = workingMap.get(colName.substring(0, colName.length()-DataToolTab.SHIFTED.length())); } if (working==null) { Dataset ySource = getDataset(colName); if(ySource==null) { return null; } working = new WorkingDataset(ySource); if(ySource.getMarkerShape()==Dataset.NO_MARKER) { ySource.setMarkerShape(Dataset.SQUARE); working.setMarkersVisible(false); } workingMap.put(colName, working); } // set x-column source of working data int labelCol = convertColumnIndexToView(0); String xName = getColumnName((labelCol==0) ? 1 : 0); Dataset xSource = getDataset(xName); if(xSource==null) { return null; } working.setXSource(xSource); // set marker and line properties of working data Dataset ySource = working.getYSource(); working.setMarkerColor(ySource.getFillColor(), ySource.getEdgeColor()); working.setMarkerSize(ySource.getMarkerSize()); working.markerType = ySource.getMarkerShape(); working.setLineColor(ySource.getLineColor()); working.setConnected(ySource.isConnected()); return working; } /** * Gets the working data: first two data columns in x-y order * * @return the working dataset */ protected WorkingDataset getWorkingData() { int n = dataManager.getDatasets().size(); if(n<2) { workingData = null; } else { int labelCol = convertColumnIndexToView(0); int yCol = (labelCol<2) ? 2 : 1; String yName = getColumnName(yCol); workingData = getWorkingData(yName); } return workingData; } /** * Removes the working data for a specified column name. * * @param colName the name of the data column */ protected void removeWorkingData(String colName) { if(colName==null) { return; } workingMap.remove(colName); setFormatPattern(colName, null); refreshTable(); } protected void deleteSelectedColumns() { ArrayList<String> colNames = getSelectedColumnNames(); int[] cols = getSelectedColumns(); for(int i = colNames.size()-1; i>-1; i--) { String name = colNames.get(i); Dataset target = getDataset(name); if(!dataToolTab.isDeletable(target)) { continue; } Dataset deleted = deleteColumn(name); if(deleted==null) { continue; } // post edit: target is column, value is dataset Integer colInt = new Integer(cols[i]); TableEdit edit = new TableEdit(DELETE_COLUMN_EDIT, name, colInt, deleted); dataToolTab.undoSupport.postEdit(edit); } refreshUndoItems(); } /** * Clears the working data. */ protected void clearWorkingData() { for(Iterator<String> it = workingMap.keySet().iterator(); it.hasNext(); ) { String colName = it.next().toString(); setFormatPattern(colName, null); } workingMap.clear(); refreshTable(); } /** * Gets the source dataset associated with table column name. * @param colName the column name * @return the dataset */ protected Dataset getDataset(String colName) { if (colName==null) return null; int i = dataManager.getDatasetIndex(colName); if (i==-1 && colName.endsWith(DataToolTab.SHIFTED)) { i = dataManager.getDatasetIndex(colName.substring(0, colName.length()-DataToolTab.SHIFTED.length())); } if (i>-1) { return dataManager.getDataset(i); } // check all datasets in dataManager to see if subscripting removed java.util.Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset next = it.next(); if(next.getYColumnName().equals(colName)) { return next; } if (colName.endsWith(DataToolTab.SHIFTED) && next.getYColumnName().equals(colName.substring(0, colName.length()-DataToolTab.SHIFTED.length()))) { return next; } } return null; } /** * Gets the selected data. The returned dataset consists of the selected * rows in the first two columns of the table in x-y order. * This also sets the highlights of the working data and * populates the workingRows map. * * @return the data in the selected rows, or all data if no rows are selected */ protected HighlightableDataset getSelectedData() { if(getWorkingData()==null) { return null; } double[] xValues, yValues; // selected data values double[] x = workingData.getXSource().getYPoints(); double[] y = workingData.getYSource().getYPoints(); // map working index to row index int workingIndex = 0; // index in working data for(int i = 0; i<x.length; i++) { if(Double.isNaN(x[i])) { continue; } workingRows.put(new Integer(workingIndex++), new Integer(i)); } workingData.clearHighlights(); // is x- or y-source column selected? int labelCol = convertColumnIndexToView(0); int xCol = (labelCol==0) ? 1 : 0; int yCol = (labelCol<2) ? 2 : 1; int[] cols = getSelectedColumns(); boolean colSelected = false; for(int k = 0; k<cols.length; k++) { colSelected = colSelected || (cols[k]==xCol) || (cols[k]==yCol); } if(!colSelected||(getSelectedRowCount()==0)) { // nothing selected xValues = x; yValues = new double[x.length]; for(int i = 0; i<yValues.length; i++) { yValues[i] = (i<y.length) ? y[i] : Double.NaN; } } else { xValues = new double[selectedRows.size()]; yValues = new double[selectedRows.size()]; int i = 0; int index = 0; // model row index workingIndex = -1; // index in working data Iterator<Integer> it = selectedRows.iterator(); while(it.hasNext()) { int row = it.next().intValue(); xValues[i] = (row>=x.length) ? Double.NaN : x[row]; yValues[i] = (row>=y.length) ? Double.NaN : y[row]; i++; // find working index of added point for(; index<=row; index++) { if(index>=x.length) { continue; } if(Double.isNaN(x[index])) { continue; } workingIndex++; } // highlight point if not NaN if((workingIndex>-1)&&(index<=x.length)&&!Double.isNaN(x[index-1])) { workingData.setHighlighted(workingIndex, true); } } } DataTool.copyDataset(workingData, selectedData, false); selectedData.clear(); selectedData.append(xValues, yValues); return selectedData; } /** * Converts a table row index to the corresponding * model row number (i.e., displayed in the "row" column). * * @param row the table row * @return the model row */ protected int getModelRow(int row) { int labelCol = convertColumnIndexToView(0); return (Integer)getValueAt(row, labelCol); } /** * Converts a model row index (i.e., displayed in the "row" column) * to the corresponding table row number. * * @param row the table row * @return the model row */ protected int getViewRow(int row) { int col = convertColumnIndexToView(0); for(int i = 0; i<getRowCount(); i++) { if(row==(Integer)getValueAt(i, col)) { return i; } } return -1; } /** * Gets the selected model rows in ascending order. * * @return the selected rows */ protected int[] getSelectedModelRows() { Integer[] a = selectedRows.toArray(new Integer[0]); int[] rows = new int[a.length]; for (int i=0; i<a.length; i++) { rows[i] = a[i]; } return rows; } /** * Sets the selected model rows. * * @param rows the model rows to select */ protected void setSelectedModelRows(int[] rows) { if(getRowCount()<1) { return; } removeRowSelectionInterval(0, getRowCount()-1); TreeSet<Integer> viewRows = new TreeSet<Integer>(); for(int i = 0; i<rows.length; i++) { int row = getViewRow(rows[i]); if(row>-1) { viewRows.add(row); } } int start = -1, end = -1; for (int next: viewRows) { if (start==-1) { start = next; end = start; continue; } if (next==end+1) { end = next; continue; } addRowSelectionInterval(start, end); start = next; end = next; } if (start>-1) { addRowSelectionInterval(start, end); } } /** * Gets the selected column names. * * @return ArrayList of selected column names */ protected ArrayList<String> getSelectedColumnNames() { int[] columns = getSelectedColumns(); ArrayList<String> names = new ArrayList<String>(); for(int i = 0; i<columns.length; i++) { int index = convertColumnIndexToModel(columns[i])-1; if(index<0) { continue; } String name = dataManager.getDataset(index).getYColumnName(); names.add(name); } return names; } /** * Sets the selected column names. * * @param names Collection of column names to select */ protected void setSelectedColumnNames(Collection<String> names) { if(getColumnCount()<1) { return; } removeColumnSelectionInterval(0, getColumnCount()-1); Iterator<String> it = names.iterator(); while(it.hasNext()) { String colName = it.next(); int index = dataManager.getDatasetIndex(colName); if(index==-1) { continue; } int col = convertColumnIndexToView(index+1); addColumnSelectionInterval(col, col); } } /** * Inserts a column dataset. * * @param data the dataset to insert * @param col the insertion view column number */ protected void insertColumn(Dataset data, int col) { data.setXColumnVisible(false); ArrayList<Dataset> datasets = dataManager.getDatasets(); // data, if added, will be last dataset int index = datasets.size(); if(index==0) { dataToolTab.originatorID = data.getID(); } // determine model and view columns of data int dataModelCol = index+1; // default if not yet added int dataViewCol = index+1; // default if not yet added // save selected rows and columns int[] rows = getSelectedModelRows(); ArrayList<String> cols = getSelectedColumnNames(); // clear selection clearSelection(); // save desired model column order TableModel model = getModel(); // modelColumns index is view column and value is model column int len = model.getColumnCount(); int[] modelColumns = new int[len+1]; modelColumns[col] = dataModelCol; for(int j = 0; j<model.getColumnCount(); j++) { // j is current view column number // modelCol is current model column number int modelCol = convertColumnIndexToModel(j); if(modelCol==dataModelCol) { continue; } // viewCol is desired view column number int viewCol = j; if(j<dataViewCol) { if(j>=col) { viewCol++; } } else { if(j<=col) { viewCol--; } } modelColumns[viewCol] = modelCol; } // add data if not present if(data instanceof DataFunction) { FunctionTool tool = dataToolTab.getDataBuilder(); FunctionPanel panel = tool.getPanel(dataToolTab.getName()); String presentation = panel.undoManager.getPresentationName(); if(panel.undoManager.canUndo()&&presentation.equals("Deletion")) { //$NON-NLS-1$ panel.undoManager.undo(); } } else { dataManager.addDataset(data); getWorkingData(data.getYColumnName()); } // refresh table and set column order DataToolTable.super.refreshTable(); // for each view column i for(int i = 0; i<modelColumns.length; i++) { // find its current model column and move it if nec for(int j = i; j<modelColumns.length; j++) { if(convertColumnIndexToModel(j)==modelColumns[i]) { if(j!=i) { moveColumn(j, i); } break; } } } // restore selected rows or select all rows if none selected if(rows.length==0) { setRowSelectionInterval(0, getRowCount()-1); } else { setSelectedModelRows(rows); } // restore selected columns but include inserted column cols.add(data.getYColumnName()); setSelectedColumnNames(cols); refreshTable(); refreshDataFunctions(); dataToolTab.statsTable.refreshStatistics(); dataToolTab.propsTable.refreshTable(); dataToolTab.refreshGUI(); dataToolTab.refreshPlot(); dataToolTab.tabChanged(true); refreshUndoItems(); } /** * Deletes a column. * * @param colName the column name to delete * @return the deleted dataset */ protected Dataset deleteColumn(String colName) { int index = dataManager.getDatasetIndex(colName); int deletedCol = convertColumnIndexToView(index+1); Dataset data = dataManager.getDataset(index); // determine if sort column is to be deleted boolean sortColDeleted = getSortedColumn()==index+1; if(sortColDeleted) { sort(0); } // save selected rows and columns int[] rows = getSelectedModelRows(); ArrayList<String> cols = getSelectedColumnNames(); // clear selection clearSelection(); // save desired model column order TableModel model = getModel(); // modelColumns index is view column and value is model column int[] modelColumns = new int[model.getColumnCount()-1]; int viewCol = -1; for(int j = 0; j<model.getColumnCount(); j++) { // j is current view column number // viewCol is desired view column number if(j==deletedCol) { continue; } viewCol++; // modelCol is current model column number int modelCol = convertColumnIndexToModel(j); // decrement modelCol if greater than deleted model column if(modelCol>index+1) { modelCol--; } modelColumns[viewCol] = modelCol; } if(data instanceof DataFunction) { FunctionTool tool = dataToolTab.getDataBuilder(); FunctionPanel panel = tool.getPanel(dataToolTab.getName()); // next line posts undo edit to FunctionPanel panel.functionEditor.removeObject(data, true); } else { dataManager.removeDataset(index); workingMap.remove(colName); } if(dataManager.getDatasets().isEmpty()) { dataToolTab.originatorID = 0; tableChanged(new TableModelEvent(getModel(), TableModelEvent.HEADER_ROW)); dataToolTab.refreshGUI(); } else { // refresh table and set column order DataToolTable.super.refreshTable(); // for each view column i for(int i = 0; i<modelColumns.length; i++) { // find its current model column and move it if nec for(int j = i; j<modelColumns.length; j++) { if(convertColumnIndexToModel(j)==modelColumns[i]) { if(j!=i) { moveColumn(j, i); } break; } } } // restore selection unless deleted column was only one selected if(!((cols.size()==1)&&cols.contains(colName))) { setSelectedModelRows(rows); setSelectedColumnNames(cols); } } refreshTable(); refreshDataFunctions(); dataToolTab.refreshPlot(); dataToolTab.propsTable.refreshTable(); dataToolTab.refreshGUI(); dataToolTab.tabChanged(true); refreshUndoItems(); dataToolTab.varPopup = null; return data; } /** * Inserts cells with values specified by column name. * Existing cells are shifted down and other columns are * padded with NaN at the end if needed. * * @param rows the model rows to insert * @param values HashMap of column name to double[] values * @return HashMap of column name to double[] inserted values */ protected HashMap<String, double[]> insertCells(int[] rows, HashMap<String, double[]> values) { int count = getRowCount(); int[] fillRows = new int[rows.length]; for(int i = 0; i<rows.length; i++) { fillRows[i] = count+i; } int[] cols = new int[values.keySet().size()]; int k = 0; HashMap<String, double[]> inserted = new HashMap<String, double[]>(); Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset next = it.next(); String colName = next.getYColumnName(); if(values.keySet().contains(colName)) { // insert cells in columns with values double[] vals = values.get(colName); vals = insertPoints(next, rows, vals); inserted.put(colName, vals); int index = dataManager.getDatasetIndex(colName); cols[k++] = convertColumnIndexToView(index+1); } // insert NaN cells at end of other columns else { insertPoints(next, fillRows, null); } } refreshDataFunctions(); refreshTable(); setSelectedModelRows(rows); setSelectedColumnNames(values.keySet()); dataToolTab.refreshPlot(); refreshUndoItems(); return inserted; } /** * Deletes cells in a column. * Remaining cells are shifted up. * * @param rows the model rows to delete * @param values HashMap of column name to (ignored) double[] values * @return HashMap of column name to double[] deleted values */ protected HashMap<String, double[]> deleteCells(int[] rows, HashMap<String, double[]> values) { int startFillRow = getRowCount()-rows.length; HashMap<String, double[]> deleted = new HashMap<String, double[]>(); // for each column in map keys, delete specified rows Iterator<String> it = values.keySet().iterator(); while(it.hasNext()) { String colName = it.next(); int index = dataManager.getDatasetIndex(colName); // identify the dataset Dataset dataset = dataManager.getDataset(index); // remove points at specified rows double[] removed = deletePoints(dataset, rows); deleted.put(colName, removed); int[] fillRows = new int[rows.length]; for(int i = 0; i<rows.length; i++) { fillRows[i] = startFillRow+i; } // insert NaN points at end insertPoints(dataset, fillRows, null); } // trim empty rows trimEmptyRows(startFillRow-1); refreshDataFunctions(); refreshTable(); setSelectedColumnNames(values.keySet()); setSelectedModelRows(rows); dataToolTab.refreshPlot(); refreshUndoItems(); return deleted; } /** * Replaces cells. * * @param rows the model rows to replace in ascending order * @param values HashMap of column name to double[] new values * @return HashMap of column name to double[] old values */ protected HashMap<String, double[]> replaceCells(int[] rows, HashMap<String, double[]> values) { int[] cols = new int[values.keySet().size()]; HashMap<String, double[]> replaced = new HashMap<String, double[]>(); Iterator<String> it = values.keySet().iterator(); int i = 0; while(it.hasNext()) { String colName = it.next(); double[] vals = values.get(colName); int index = dataManager.getDatasetIndex(colName); Dataset data = dataManager.getDataset(index); double[] pts = replacePoints(data, rows, vals); replaced.put(colName, pts); cols[i++] = convertColumnIndexToView(index+1); } refreshDataFunctions(); refreshTable(); setSelectedModelRows(rows); setSelectedColumnNames(values.keySet()); refreshUndoItems(); dataToolTab.refreshPlot(); return replaced; } /** * Inserts rows with values specified by column name. * Unspecified values are set to NaN. * * @param rows the model rows to insert * @param values HashMap of column name to double[] values * @return HashMap of column name to double[] inserted values */ protected HashMap<String, double[]> insertRows(int[] rows, HashMap<String, double[]> values) { if(values==null) { values = new HashMap<String, double[]>(); } // ensure every column is included ArrayList<Dataset> datasets = dataManager.getDatasets(); for(int i = 0; i<datasets.size(); i++) { Dataset next = datasets.get(i); String name = next.getYColumnName(); if(!values.keySet().contains(name)) { values.put(name, null); } } // insert cells into all columns values = insertCells(rows, values); // scroll table to end if end row is newly inserted int endRow = getModelRow(getRowCount()-1); for(int i = 0; i<rows.length; i++) { if(endRow==rows[i]) { java.awt.Rectangle rect = getVisibleRect(); rect.y = getSize().height-rect.height+getRowHeight(); scrollRectToVisible(rect); break; } } return values; } /** * Deletes rows. * * @param rows the model rows to delete * @return the deleted values */ protected HashMap<String, double[]> deleteRows(int[] rows) { HashMap<String, double[]> removed = new HashMap<String, double[]>(); // remove points from every dataset Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset next = it.next(); double[] cells = deletePoints(next, rows); removed.put(next.getYColumnName(), cells); } refreshTable(); refreshDataFunctions(); clearSelection(); setSelectedColumnNames(removed.keySet()); setSelectedModelRows(rows); refreshUndoItems(); dataToolTab.refreshPlot(); return removed; } /** * Determines if a row is empty. * * @param row the model row number * @return true if all datasets are NaN at row index */ protected boolean isEmptyRow(int row) { boolean empty = true; Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset data = it.next(); if(data instanceof DataFunction) { continue; } double[] y = data.getYPoints(); if(row>=y.length) { return false; } empty = empty&&Double.isNaN(y[row]); } return empty; } /** * Determines if a row is empty. * * @param row the model row number * @param columnNames a list of column names * @return true if all named columns are NaN at row index */ protected boolean isEmptyCells(int row, ArrayList<String> columnNames) { boolean empty = true; Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset data = it.next(); String name = data.getYColumnName(); if((data instanceof DataFunction)||!columnNames.contains(name)) { continue; } double[] y = data.getYPoints(); if(row>=y.length) { return false; } empty = empty&&Double.isNaN(y[row]); } return empty; } /** * Gets the x-axis view column. * * @return col the view column number */ protected int getXColumn() { if(getColumnCount()<2) { return -1; } int labelCol = convertColumnIndexToView(0); return(labelCol==0) ? 1 : 0; } /** * Gets the y-axis view column. * * @return col the view column number */ protected int getYColumn() { if(getColumnCount()<3) { return -1; } int labelCol = convertColumnIndexToView(0); return(labelCol<2) ? 2 : 1; } /** * Replaces points in a dataset. * * @param dataset the dataset * @param rows the rows to replace in ascending order * @param vals array of new y-values * @return array of values replaced */ protected double[] replacePoints(Dataset dataset, int[] rows, double[] vals) { double[] replaced = new double[rows.length]; DataColumn column = null; boolean shifted = false; if (dataset instanceof DataColumn) { column = (DataColumn)dataset; shifted = column.isShifted(); column.setShifted(false); } double[] x = dataset.getXPoints(); // determine required row count int count = x.length; for(int i = 0; i<rows.length; i++) { count = Math.max(count, rows[i]+1); } // add rows if required while(count>x.length) { int[] row = new int[] {x.length}; insertRows(row, null); x = dataset.getXPoints(); } double[] y = dataset.getYPoints(); for(int i = 0; i<rows.length; i++) { replaced[i] = y[rows[i]]; y[rows[i]] = (vals==null) ? Double.NaN : vals[i]; } dataset.clear(); dataset.append(x, y); if (column!=null) { column.setShifted(shifted); } dataToolTab.tabChanged(true); return replaced; } /** * Inserts points into a dataset. * * @param dataset the dataset * @param rows the rows to insert in ascending order * @param vals the corresponding y-values to insert * @return array of values inserted */ protected double[] insertPoints(Dataset dataset, int[] rows, double[] vals) { if(vals==null) { vals = new double[rows.length]; for(int i = 0; i<vals.length; i++) { vals[i] = Double.NaN; } } if(dataset instanceof DataFunction) { return vals; } // insert values starting with lowest row double[] y = dataset.getYPoints(); for(int i = 0; i<rows.length; i++) { int n = y.length; double[] newy = new double[n+1]; System.arraycopy(y, 0, newy, 0, rows[i]); System.arraycopy(y, rows[i], newy, rows[i]+1, n-rows[i]); newy[rows[i]] = vals[i]; y = newy; } double[] x = DataTool.getRowArray(y.length); dataset.clear(); dataset.append(x, y); dataToolTab.tabChanged(true); return vals; } /** * Deletes points from a dataset. * * @param dataset the dataset * @param rows the rows to remove in ascending order * @return the removed y-values */ protected double[] deletePoints(Dataset dataset, int[] rows) { double[] removed = new double[rows.length]; if(dataset instanceof DataFunction) { return removed; } // remove y-values starting with highest row double[] y = dataset.getYPoints(); for(int i = rows.length-1; i>-1; i--) { int n = y.length; double[] newy = new double[n-1]; System.arraycopy(y, rows[i], removed, i, 1); if(rows[i]>0) { System.arraycopy(y, 0, newy, 0, rows[i]); } if(rows[i]<n-1) { System.arraycopy(y, rows[i]+1, newy, rows[i], n-rows[i]-1); } y = newy; } double[] x = DataTool.getRowArray(y.length); dataset.clear(); dataset.append(x, y); dataToolTab.tabChanged(true); return removed; } /** * Trims empty rows from bottom of table up to a specified minimum. * * @param minSize the minimum row count to keep */ protected void trimEmptyRows(int minSize) { clearSelection(); int endRow = getRowCount()-1; boolean empty = true; int[] rows = new int[1]; while(empty&&(endRow>minSize)) { empty = isEmptyRow(endRow); if(empty) { rows[0] = endRow; deleteRows(rows); endRow--; } } if(getSelectedRows().length==0) { removeColumnSelectionInterval(0, getColumnCount()-1); } } /** * Clears the selection if it consists of only an empty end row. */ protected void clearSelectionIfEmptyEndRow() { if(getRowCount()<2) { return; } int[] selectedRows = getSelectedRows(); int endRow = getRowCount()-1; if((selectedRows.length==1)&&(selectedRows[0]==endRow)&&isEmptyRow(endRow)) { clearSelection(); } } /** * Displays the data builder. */ protected void showDataBuilder() { FunctionTool tool = dataToolTab.getDataBuilder(); tool.setSelectedPanel(dataToolTab.getName()); tool.setVisible(true); } /** * Renames a column. * * @param oldName the old name * @param newName the new name */ protected void renameColumn(String oldName, String newName) { int index = dataManager.getDatasetIndex(oldName); Dataset data = dataManager.getDataset(index); data.setXYColumnNames(data.getXColumnName(), newName); refreshDataFunctions(); dataToolTab.columnNameChanged(oldName, newName); refreshTable(); refreshUndoItems(); } /** * Refreshes the undo and redo menu items. */ protected void refreshUndoItems() { if (dataToolTab!=null) { dataToolTab.refreshUndoItems(); } } /** * Refreshes the data functions. */ public void refreshDataFunctions() { java.util.Iterator<Dataset> it = dataManager.getDatasets().iterator(); while(it.hasNext()) { Dataset next = it.next(); if(next instanceof DataFunction) { ((DataFunction) next).refreshFunctionData(); } } } /** * Selects all cells in the table. */ public void selectAllCells() { selectAll(); requestFocusInWindow(); } /** * Deselects all selected columns and rows. Overrides JTable method. */ public void clearSelection() { if(workingData!=null) { workingData.clearHighlights(); } if(selectedData!=null) { selectedData.clearHighlights(); } // select only the focus cell so it has focus if ((focusRow>-1)&&(focusRow<getRowCount())&&(focusCol>0)&&(focusCol<getColumnCount())) { setRowSelectionInterval(focusRow, focusRow); setColumnSelectionInterval(focusCol, focusCol); } leadCol = 0; leadRow = 0; super.clearSelection(); repaint(); } /** * Refreshes the data in the table. Overrides DataTable method. */ public void refreshTable() { // save model column order int[] modelColumns = getModelColumnOrder(); // save selected rows and columns int[] rows = getSelectedModelRows(); ArrayList<String> cols = getSelectedColumnNames(); boolean noView = convertColumnIndexToView(0)==-1; // refresh table super.refreshTable(); if(noView) { return; } // restore column order--but keep "tabChanged" unchanged boolean changed = dataToolTab.tabChanged; setModelColumnOrder(modelColumns); dataToolTab.tabChanged(changed); // re-sort to restore row order sort(getSortedColumn()); // restore selected rows and columns // important to select columns first! if(!cols.isEmpty()) { setSelectedColumnNames(cols); } if(rows.length>0) { setSelectedModelRows(rows); } } @Override public NumberFormatDialog getFormatDialog(String[] names, String[] selected) { for (int i=0; i<names.length; i++) { if (names[i].endsWith(DataToolTab.SHIFTED)) { names[i] = names[i].substring(0, names[i].length()-DataToolTab.SHIFTED.length()); } } return super.getFormatDialog(names, selected); } /** * Gets the model column order. * * @return array of model column numbers in view column order */ public int[] getModelColumnOrder() { int[] modelColumns = new int[getModel().getColumnCount()]; for(int i = 0; i<modelColumns.length; i++) { modelColumns[i] = convertColumnIndexToModel(i); } return modelColumns; } /** * Sets the model column order. * * @param modelColumns array of model column numbers in view column order */ public void setModelColumnOrder(int[] modelColumns) { if(modelColumns==null) { return; } // for each model column i for(int i = 0; i<modelColumns.length; i++) { // find its current view column and move it if needed for(int j = i; j<modelColumns.length; j++) { if(convertColumnIndexToModel(j)==modelColumns[i]) { if(j!=i) { moveColumn(j, i); } break; } } } } /** * Gets the names of columns with visible markers. * * @return array of column names */ public String[] getHiddenMarkers() { ArrayList<String> list = new ArrayList<String>(); for(int i = 0; i<getColumnCount(); i++) { String name = getColumnName(i); WorkingDataset next = getWorkingData(name); if((next!=null)&&!next.isMarkersVisible()) { list.add(name); } } return list.toArray(new String[list.size()]); } /** * Hides markers of named columns. * * @param hiddenColumns names of columns with hidden markers */ public void hideMarkers(String[] hiddenColumns) { if(hiddenColumns==null) { return; } for(int i = 0; i<hiddenColumns.length; i++) { String name = hiddenColumns[i]; WorkingDataset next = getWorkingData(name); if(next!=null) { next.setMarkersVisible(false); } } } /** * Sets the working columns by name. * * @param xColName the name of the horizontal axis variable * @param yColName the name of the vertical axis variable */ public void setWorkingColumns(String xColName, String yColName) { // move labels to column 0 int labelCol = convertColumnIndexToView(0); getColumnModel().moveColumn(labelCol, 0); // find xCol and move to column 1 TableModel model = getModel(); for(int i = 1; i<model.getColumnCount(); i++) { if(xColName.equals(getColumnName(i))) { getColumnModel().moveColumn(i, 1); break; } } // find y and move to column 2 for(int i = 2; i<model.getColumnCount(); i++) { if(yColName.equals(getColumnName(i))) { getColumnModel().moveColumn(i, 2); break; } } } public void setFont(Font font) { super.setFont(font); if(labelRenderer!=null) { Font labelFont = labelRenderer.getFont(); labelFont = labelFont.deriveFont(font.getSize2D()); labelRenderer.setFont(labelFont); headerRenderer.headerFont = labelFont; rowNumberRenderer.setFont(labelFont); } setRowHeight(font.getSize()+4); } /** * Sets the label column width * @param w the width */ protected void setLabelColumnWidth(int w) { labelColumnWidth = w; } /** * Overrides DataTable getCellRenderer() method. * * @param row the row number * @param col the column number * @return the cell editor */ public TableCellRenderer getCellRenderer(int row, int col) { TableCellRenderer renderer = super.getCellRenderer(row, col); if(renderer==rowNumberRenderer) { return labelRenderer; } dataRenderer.renderer = renderer; return dataRenderer; } /** * Returns the editor for a cell specified by row and column. * * @param row the row number * @param col the column number * @return the cell editor */ public TableCellEditor getCellEditor(int row, int col) { editor.setColumn(col); return editor; } /** * Returns the minimum table width. * * @return minimum table width. */ protected int getMinimumTableWidth() { int n = getColumnCount()-1; return labelColumnWidth+n*minimumDataColumnWidth; } /** * A header cell renderer that identifies sorted and selected columns. */ class HeaderRenderer implements TableCellRenderer { TableCellRenderer renderer; Font headerFont; DrawingPanel panel = new DrawingPanel(); DrawableTextLine textLine = new DrawableTextLine("", 0, -6); //$NON-NLS-1$ /** * Constructor HeaderRenderer * @param renderer */ public HeaderRenderer(TableCellRenderer renderer) { this.renderer = renderer; textLine.setJustification(TextLine.CENTER); panel.addDrawable(textLine); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { // value is data column name String realname = (value==null) ? "" : value.toString(); //$NON-NLS-1$ String name = realname; if (OSPRuntime.isMac()) { name = TeXParser.removeSubscripting(name); } Component c = renderer.getTableCellRendererComponent(table, name, isSelected, hasFocus, row, col); if (headerFont==null) headerFont = c.getFont(); int labelCol = convertColumnIndexToView(0); int xCol = (labelCol==0) ? 1 : 0; int yCol = (labelCol<2) ? 2 : 1; if(unselectedBG==null) { unselectedBG = c.getBackground(); } // backup color in case c has none if(unselectedBG==null) { unselectedBG = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$ } rowBG = dataToolTab.plot.getBackground(); Color bgColor = (col==xCol)? DataToolTable.xAxisColor: (col==yCol)? DataToolTable.yAxisColor: rowBG; if(!(c instanceof JComponent)) { return c; } JComponent comp = (JComponent) c; java.awt.Dimension dim = comp.getPreferredSize(); dim.height += 1; dim.height = Math.max(getRowHeight()+2, dim.height); panel.setPreferredSize(dim); javax.swing.border.Border border = comp.getBorder(); if(border instanceof javax.swing.border.EmptyBorder) { border = BorderFactory.createLineBorder(Color.LIGHT_GRAY); } panel.setBorder(border); // determine font: italics if undeletable, bold if sorted column Font font; Dataset data = getDataset(realname); if(!dataToolTab.isDeletable(data)) { font = getSortedColumn()!=convertColumnIndexToModel(col)? headerFont.deriveFont(Font.PLAIN+Font.ITALIC) : headerFont.deriveFont(Font.BOLD+Font.ITALIC); } else { font = getSortedColumn()!=convertColumnIndexToModel(col)? headerFont.deriveFont(Font.PLAIN) : headerFont.deriveFont(Font.BOLD); } int[] cols = getSelectedColumns(); boolean selected = false; for(int i = 0; i<cols.length; i++) { selected = selected||(cols[i]==col); } selected = selected&&(convertColumnIndexToModel(col)>0); bgColor = selected? selectedHeaderBG: bgColor; // special case: textline doesn't work on OSX if (OSPRuntime.isMac()) { comp.setFont(font); comp.setBackground(bgColor); comp.setForeground(selected ? selectedHeaderFG : comp.getForeground()); if (comp instanceof JLabel) { ((JLabel)comp).setHorizontalAlignment(SwingConstants.CENTER); } return comp; } textLine.setText(name); textLine.setFont(font); textLine.setColor(selected ? selectedHeaderFG : comp.getForeground()); textLine.setBackground(bgColor); panel.setBackground(bgColor); return panel; } } /** * A dataset whose y values and display properties depend on a source dataset. */ class WorkingDataset extends HighlightableDataset { final private Dataset yData; private Dataset xData; boolean markersVisible; int markerType; boolean isWorkingYColumn; /** * Constructor WorkingDataset * @param yDataset */ public WorkingDataset(Dataset yDataset) { yData = yDataset; setColor(yData.getFillColor(), yData.getLineColor()); markerType = yData.getMarkerShape(); setMarkerShape(markerType); markersVisible = (markerType!=Dataset.NO_MARKER); if(markerType==Dataset.NO_MARKER) { markerType = Dataset.CIRCLE; } setMarkerSize(yData.getMarkerSize()); setConnected(yData.isConnected()); } @Override public void draw(DrawingPanel drawingPanel, java.awt.Graphics g) { if(isWorkingYColumn) { drawSurrounds(drawingPanel, (Graphics2D)g); } boolean vis = markersVisible; if(isWorkingYColumn&&!vis) { setMarkersVisible(true); } super.draw(drawingPanel, g); if(isWorkingYColumn&&!vis) { setMarkersVisible(false); } } /** * Draw green shapes surrounding the working data points. * * @param drawingPanel * @param g2 */ protected void drawSurrounds(DrawingPanel drawingPanel, Graphics2D g2) { // set up graphics Color c = g2.getColor(); Stroke s = g2.getStroke(); g2.setColor(new Color(51, 255, 51, 153)); g2.setStroke(new BasicStroke(2)); // set up shape size Shape shape = null; int radius = getMarkerSize()+2; int marker = getMarkerShape(); if (marker==NO_MARKER || marker==PIXEL) { radius = 3; } int size = radius*2+1; // get data points double[] tempX = getXPoints(); double[] tempY = getYPoints(); double xp = 0; double yp = 0; // draw surrounds for(int i = 0; i<index; i++) { if(Double.isNaN(tempY[i])) { continue; } if(drawingPanel.isLogScaleX()&&(tempX[i]<=0)) { continue; } if(drawingPanel.isLogScaleY()&&(tempY[i]<=0)) { continue; } xp = drawingPanel.xToPix(tempX[i]); yp = drawingPanel.yToPix(tempY[i]); if (marker==SQUARE || marker==POST || marker==BAR) { shape = new Rectangle2D.Double(xp-radius, yp-radius, size, size); } else shape = new Ellipse2D.Double(xp-radius, yp-radius, size, size); g2.draw(shape); } // restore graphics g2.setColor(c); g2.setStroke(s); } public boolean isMarkersVisible() { return markersVisible||isWorkingYColumn; } public void setMarkersVisible(boolean visible) { if(!visible&&markersVisible) { markerType = getMarkerShape(); setMarkerShape(Dataset.NO_MARKER); } else if(visible) { setMarkerShape(markerType); } markersVisible = visible; } public void setColor(Color edgeColor, Color lineColor) { Color fill = new Color(edgeColor.getRed(), edgeColor.getGreen(), edgeColor.getBlue(), 100); setMarkerColor(fill, edgeColor); setLineColor(lineColor); yData.setMarkerColor(fill, edgeColor); yData.setLineColor(lineColor); } public void setConnected(boolean connected) { super.setConnected(connected); yData.setConnected(connected); } public void setMarkerSize(int size) { super.setMarkerSize(size); yData.setMarkerSize(size); } public void setMarkerShape(int shape) { super.setMarkerShape(shape); if(shape!=Dataset.NO_MARKER) { yData.setMarkerShape(shape); markerType = shape; } } Dataset getYSource() { return yData; } Dataset getXSource() { return xData; } void setXSource(Dataset xDataset) { xData = xDataset; clear(); double[] x = xData.getYPoints(); double[] y = yData.getYPoints(); if(x.length!=y.length) { int n = Math.min(x.length, y.length); double[] nx = new double[n]; System.arraycopy(x, 0, nx, 0, n); double[] ny = new double[n]; System.arraycopy(y, 0, ny, 0, n); append(nx, ny); } else { append(x, y); } setXYColumnNames(xData.getYColumnName(), yData.getYColumnName()); } } /** * A class to render data cells. */ class DataCellRenderer implements TableCellRenderer { TableCellRenderer renderer; boolean showFocus = false; Color unlockedBG = Color.WHITE, lockedBG = new Color(255, 220, 0, 30); public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { int modelCol = convertColumnIndexToModel(col); if((hasFocus || isSelected) && modelCol>0) { focusRow = row; focusCol = col; } if(selectedBG==null) { Component c = renderer.getTableCellRendererComponent(table, value, true, false, row, col); selectedBG = c.getBackground(); selectedFG = c.getForeground(); selectedHeaderFG = selectedFG.darker(); float[] hsb = Color.RGBtoHSB(selectedBG.getRed(), selectedBG.getGreen(), selectedBG.getBlue(), null); hsb[2] *= 0.85f; int darker = Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]); selectedHeaderBG = new Color(darker); } if(!showFocus) { hasFocus = ((col==mouseCol)&&(row==mouseRow)); } Component c = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); Dataset data = dataManager.getDataset(modelCol-1); if(!isSelected) { c.setBackground(dataToolTab.isDeletable(data) ? unlockedBG : lockedBG); } return c; } } /** * A class to render labels such as row number. Also used by * DataToolPropsTable and DataToolStatsTable. */ class LabelRenderer extends JLabel implements TableCellRenderer { /** * Constructor LabelRenderer */ public LabelRenderer() { setOpaque(true); // make background visible. setHorizontalAlignment(SwingConstants.RIGHT); } public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { setText((value==null) ? null : value.toString()); setEnabled(true); boolean selected = false; if(table==DataToolTable.this) { setEnabled((row<getRowCount()-1)||!isEmptyRow(row)); int[] rows = getSelectedRows(); for(int i = 0; i<rows.length; i++) { selected = selected||(rows[i]==row); } } // setForeground(selected ? selectedFG : Color.black); // setBackground(selected ? selectedBG : unselectedBG); setForeground(selected ? selectedHeaderFG : Color.black); setBackground(selected ? selectedHeaderBG : unselectedBG); return this; } } /** * A table model for this table. */ protected static class DataToolTableModel extends DataTable.DefaultDataTableModel { DataToolTab tab; DataToolTableModel(DataToolTab tab) { this.tab = tab; } public String getColumnName(int col) { if(col==0) { return rowName; } String name = tab.dataManager.getColumnName(col-1); if (tab.originShiftEnabled && tab.plot!=null) { name = name + DataToolTab.SHIFTED; } return name; } // @Override // public Object getValueAt(int row, int col) { // if (tab.offsetOriginVisible && col>0) { // double val = (Double)super.getValueAt(row, col); // DataColumn dataCol = (DataColumn)tab.dataTable.getDataset(getColumnName(col)); // if (dataCol!=null) { // val -= dataCol.getShift(); // } // return val; // } // return super.getValueAt(row, col); // } // public void setValueAt(Object value, int row, int col) { if(value==null) { return; } Dataset data = tab.dataTable.dataManager.getDataset(col-1); double[] y = data.getYPoints(); double val = Double.NaN; try { val = Double.parseDouble(value.toString()); if(y[row]==val) { return; // no change } } catch(NumberFormatException e) { if(Double.isNaN(y[row])) { return; // no change } } String name = data.getYColumnName(); int[] rows = new int[] {row}; HashMap<String, double[]> map = new HashMap<String, double[]>(); map.put(name, new double[] {val}); HashMap<String, double[]> old = tab.dataTable.replaceCells(rows, map); // post edit: target is rows, value is HashMap[] {undo, redo} TableEdit edit = tab.dataTable.new TableEdit(REPLACE_CELLS_EDIT, name, rows, new HashMap[] {old, map}); tab.undoSupport.postEdit(edit); } public boolean isCellEditable(int row, int col) { return (col>0 && tab.isUserEditable()); } } /** * A cell editor for this table. */ class DataEditor extends AbstractCellEditor implements TableCellEditor { JTextField field = new JTextField(); int column; boolean isFunction; // Constructor. DataEditor() { field.setHorizontalAlignment(SwingConstants.RIGHT); field.setBorder(BorderFactory.createEmptyBorder(0, 1, 1, 0)); field.setSelectionColor(new Color(204, 255, 255)); field.addKeyListener(new KeyAdapter() { public void keyPressed(final KeyEvent e) { if(e.getKeyCode()==KeyEvent.VK_ENTER) { stopCellEditing(); Runnable runner = new Runnable() { public synchronized void run() { int row = getModelRow(focusRow)+1; // add empty row if needed if(row==getRowCount()) { insertRows(new int[] {row}, null); } row = getViewRow(row); changeSelection(row, column, false, false); editCellAt(row, column, e); field.requestFocus(); } }; SwingUtilities.invokeLater(runner); } else if(field.isEnabled()) { field.setBackground(Color.yellow); } } }); field.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent e) { if(field.getBackground()!=Color.white) { stopCellEditing(); } } public void focusGained(FocusEvent e) { field.selectAll(); } }); field.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { // right-click: pass e to the table mouse listener if(OSPRuntime.isPopupTrigger(e)) { stopCellEditing(); tableMouseListener.mousePressed(e); } } }); } void setColumn(int col) { column = col; int modelCol = convertColumnIndexToModel(col); Dataset data = dataManager.getDataset(modelCol-1); isFunction = (data instanceof DataFunction); } // Gets the component to be displayed while editing. public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int col) { // if editing a function, simply show function builder if(isFunction) { showDataBuilder(); return null; } field.setText((value==null) ? "" : String.valueOf(value)); //$NON-NLS-1$ field.setFont(DataToolTable.this.getFont()); return field; } // Determines when editing starts. public boolean isCellEditable(EventObject e) { if((e instanceof MouseEvent)&&((MouseEvent) e).getClickCount()==2) { return true; } if(e instanceof ActionEvent) { return true; } if(e instanceof KeyEvent) { return true; } return false; } // Called when editing is completed. public Object getCellEditorValue() { DataToolTable.this.requestFocusInWindow(); field.setBackground(Color.white); return field.getText(); } } /** * A class to undo/redo datatable edits. */ protected class TableEdit extends AbstractUndoableEdit { Object target, value; int editType; String columnName; HashMap<String, double[]> map; /** * Contructor. * * @param type may be * RENAME_COLUMN_EDIT, DELETE_COLUMN_EDIT, * ADD_CELLS_EDIT, DELETE_CELLS_EDIT, * ADD_ROWS_EDIT, DELETE_ROWS_EDIT, VALUE_EDIT * @param colName the column name * @param target the target rows or column * @param value the value */ public TableEdit(int type, String colName, Object target, Object value) { editType = type; columnName = colName; this.target = target; this.value = value; String name = (colName==null) ? null : ": column \""+colName+"\""; //$NON-NLS-1$ //$NON-NLS-2$ OSPLog.finer(editTypes[type]+name); } // undoes the change @SuppressWarnings("unchecked") public void undo() throws CannotUndoException { super.undo(); OSPLog.finer("undoing "+editTypes[editType]); //$NON-NLS-1$ switch(editType) { case RENAME_COLUMN_EDIT : { // columnName is new name, value is undo name //String newName = value.toString(); renameColumn(columnName, value.toString()); break; } case INSERT_COLUMN_EDIT : { // target is column, value is dataset deleteColumn(columnName); break; } case DELETE_COLUMN_EDIT : { // target is column, value is dataset Dataset data = (Dataset) value; int col = ((Integer) target).intValue(); insertColumn(data, col); break; } case INSERT_CELLS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; deleteCells(rows, values); break; } case DELETE_CELLS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; insertCells(rows, values); break; } case INSERT_ROWS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; deleteRows(rows); break; } case DELETE_ROWS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; insertRows(rows, values); break; } case REPLACE_CELLS_EDIT : { // target is rows, value is HashMap[] {undo, redo} int[] rows = (int[]) target; @SuppressWarnings("rawtypes") HashMap[] values = (HashMap[]) value; replaceCells(rows, values[0]); break; } } } // redoes the change @SuppressWarnings("unchecked") public void redo() throws CannotUndoException { super.redo(); OSPLog.finer("redoing "+editTypes[editType]); //$NON-NLS-1$ switch(editType) { case RENAME_COLUMN_EDIT : { // columnName is new name, value is undo name renameColumn(value.toString(), columnName); break; } case INSERT_COLUMN_EDIT : { // target is column, value is dataset Dataset data = (Dataset) value; int col = ((Integer) target).intValue(); insertColumn(data, col); break; } case DELETE_COLUMN_EDIT : { // target is column, value is dataset deleteColumn(columnName); break; } case INSERT_CELLS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; insertCells(rows, values); break; } case DELETE_CELLS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; deleteCells(rows, values); break; } case INSERT_ROWS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; HashMap<String, double[]> values = (HashMap<String, double[]>) value; insertRows(rows, values); break; } case DELETE_ROWS_EDIT : { // target is rows, value is HashMap int[] rows = (int[]) target; deleteRows(rows); break; } case REPLACE_CELLS_EDIT : { // target is rows, value is HashMap[] {undo, redo} int[] rows = (int[]) target; @SuppressWarnings("rawtypes") HashMap[] values = (HashMap[]) value; replaceCells(rows, values[1]); break; } } } // returns the presentation name public String getPresentationName() { return "Edit"; //$NON-NLS-1$ } } } /* * Open Source Physics software is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be * released under the GNU GPL license. * * This software 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 * this; if not, write to the Free Software Foundation, Inc., 59 Temple Place, * Suite 330, Boston MA 02111-1307 USA or view the license online at * http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */