/** * @author [email protected] * @since Jun 4, 2003 */ public abstract class AbstractViperTable extends JPanel implements ViperTableTabComponent { private Logger logger = Logger.getLogger("edu.umd.cfar.lamp.viper.gui.table"); private ViperViewMediator mediator; public abstract Descriptor getSelectedRow(); protected JPopupMenu popup; private AttributeRenderer ar; private AttributeEditor ae; private TablePanel outerTablePanel; /** * Get the model of the currently selected table (since a vipertable may have more than one table * model, like the content pane). * * @return the table model that has the user focus */ public ViperTableModel getCurrentModel() { TableModel mod = getTable() == null ? null : getTable().getModel(); if (mod instanceof ViperTableModel) { return (ViperTableModel) getTable().getModel(); } else { return null; } } public void setCurrentModel(ViperTableModel model) { getTable().setModel(model); } private ChangeListener hiddenNodesChangeListener = new ChangeListener() { public void stateChanged(ChangeEvent e) { AbstractViperTable.this.getTable().getTableHeader().repaint(); } }; private class ProxyTableCellRenderer implements TableCellRenderer { private TableCellRenderer candidate; public ProxyTableCellRenderer(TableCellRenderer delegate) { this.candidate = delegate; } /** @inheritDoc */ public boolean equals(Object arg0) { return candidate.equals(arg0); } /** @inheritDoc */ public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = candidate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (c instanceof JLabel && table != null) { ViperTableModel m = getCurrentModel(); int modelIndex = table.convertColumnIndexToModel(column); AttrConfig ac = m.getAttributeForColumn(modelIndex); JLabel l = (JLabel) c; if (ac != null) { int visibility = mediator.getHiders().getAttrConfigVisibility(ac); l.setIcon(outerTablePanel.visibilityIcons[visibility]); } else if (m.getInternalColumn(modelIndex) == ViperTableModel.BY_VALID) { Config config = m.getConfig(); int visibility = mediator.getHiders().getConfigVisibility(m.getConfig()); if (visibility == NodeVisibilityManager.RANGE_LOCKED) { visibility = NodeVisibilityManager.LOCKED; } l.setIcon(outerTablePanel.visibilityIcons[visibility]); } else { l.setIcon(null); } } return c; } /** @inheritDoc */ public int hashCode() { return candidate.hashCode(); } /** @inheritDoc */ public String toString() { return candidate.toString(); } } /** * Adds the default renderers and editors for all known data types * * @param table */ private void initAttributeTable(final EnhancedTable table) { ar = new AttributeRenderer(this); ae = new AttributeEditor(this); ae.setEditClickCount(2); TableCellRenderer r = table.getTableHeader().getDefaultRenderer(); table.getTableHeader().setDefaultRenderer(new ProxyTableCellRenderer(r)); table.setDefaultRenderer(Descriptor.class, ar); table.setDefaultRenderer(Attribute.class, ar); table.setDefaultEditor(Attribute.class, ae); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.addTableListener( new TableListener() { public void contextClick(TableEvent e) { // TODO: Should display context menu offering: sort ascending/descending; show/hide/lock } public void actionClick(TableEvent e) {} public void click(TableEvent e) { if (e.getRow() == -1) { ViperTableModel m = getCurrentModel(); int modelIndex = table.convertColumnIndexToModel(e.getColumn()); AttrConfig ac = m.getAttributeForColumn(modelIndex); NodeVisibilityManager H = mediator.getHiders(); if (ac != null) { int oldV = H.getAttrConfigVisibility(ac); H.setVisibilityByAttrConfig(ac, NodeVisibilityManager.ROTATE_VISIBILITY[oldV]); } else if (m.getInternalColumn(modelIndex) == ViperTableModel.BY_VALID) { Config config = m.getConfig(); int oldV = H.getConfigVisibility(config); H.setVisibilityByConfig( config, NodeVisibilityManager.ROTATE_RANGE_VISIBILITY[oldV]); } } } public void altClick(TableEvent e) {} }); } private int rowEditPolicy = ALLOW_ROW_EDIT; public int getRowEditPolicy() { return rowEditPolicy; } public void setRowEditPolicy(int policy) { rowEditPolicy = policy; } public static int NO_ROW_EDIT = 0; public static int ALLOW_ROW_EDIT = 1; // added by Ping on 10/31/2000 // for toggle through objects public static boolean ENABLE = true; public static boolean DISABLE = false; // Handle some of the common steps between creating the content // and object tables. public AbstractViperTable(TablePanel tp) { super(); this.outerTablePanel = tp; setLayout(new BorderLayout()); EnhancedTable table = new EnhancedTable() { public void changeSelection( int rowIndex, int columnIndex, boolean toggle, boolean extend) { ViperTableModel currModel = AbstractViperTable.this.getCurrentModel(); columnIndex = convertColumnIndexToModel(columnIndex); AttrConfig ac = currModel.getAttributeForColumn(columnIndex); Descriptor d = currModel.getDescriptorAtRow(rowIndex); Node n = null; if (ac != null) { Attribute a = d.getAttribute(ac); n = a; } else if (currModel.getInternalColumn(columnIndex) == ViperTableModel.BY_ID) { n = d; } if (n != null) { if (extend) { mediator.getSelection().addNode(n); } else { mediator.getSelection().setTo(n); } } } public boolean isCellSelected(int row, int column) { ViperTableModel currModel = AbstractViperTable.this.getCurrentModel(); column = convertColumnIndexToModel(column); AttrConfig ac = currModel.getAttributeForColumn(column); Descriptor d = currModel.getDescriptorAtRow(row); if (ac != null) { Attribute a = d.getAttribute(ac); return mediator.getSelection().isSelected(a); } else if (currModel.getInternalColumn(column) == ViperTableModel.BY_ID) { return mediator.getSelection().isSelected(d); } return false; } }; table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); table.resizeAllColumnsToNaturalWidth(); table.setCellSelectionBackground( table.getSelectionBackground().brighter().brighter().brighter()); table.setCellSelectionForeground(table.getForeground().darker()); initAttributeTable(table); JScrollPane scrollPane = new JScrollPane(table); this.add(scrollPane); popup = new DescPropPopup(); popup.setInvoker(getTable()); getTable() .addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } }); } protected abstract void maybeShowPopup(MouseEvent e); protected EnhancedTable getTable() { JScrollPane scrollPane = (JScrollPane) this.getComponent(0); return (EnhancedTable) scrollPane.getViewport().getView(); } public ViperViewMediator getMediator() { return mediator; } public void setMediator(ViperViewMediator mediator) { if (this.mediator != mediator) { if (this.mediator != null) { this.mediator.getHiders().removeChangeListener(hiddenNodesChangeListener); } this.mediator = mediator; if (this.mediator != null) { this.mediator.getHiders().addChangeListener(hiddenNodesChangeListener); } } } public void scrollToAttribute(Attribute a) { if (!a.getDescriptor().getConfig().equals(getConfig())) { logger.fine("Cannot scroll to attribute that isn't attached to this type of descriptor"); return; } int rowIndex = getCurrentModel().getRowForDescriptor(a.getDescriptor()); int colIndex = getCurrentModel().getColumnForAttribute(a); JScrollPane scrollPane = (JScrollPane) this.getComponent(0); JViewport viewport = (JViewport) scrollPane.getViewport(); EnhancedTable table = (EnhancedTable) viewport.getView(); // This rectangle is relative to the table where the // northwest corner of cell (0,0) is always (0,0). Rectangle rect = table.getCellRect(rowIndex, colIndex, true); // The location of the viewport relative to the table Point pt = viewport.getViewPosition(); // Translate the cell location so that it is relative // to the view, assuming the northwest corner of the // view is (0,0) rect.setLocation(rect.x - pt.x, rect.y - pt.y); // Scroll the area into view viewport.scrollRectToVisible(rect); } // XXX Move to TablePanel - here there is one copy for each descriptor // config private class DescPropPopup extends JPopupMenu { private JCheckBoxMenuItem v; private JCheckBoxMenuItem p; private JMenuItem delete; private JMenuItem duplicate; private JMenuItem interp; private JCheckBoxMenuItem wrt; private JMenu interpToMark; private JMenuItem shift; private JMenu shiftToMark; private ShiftToMarkAction stmAction; private InterpToMarkAction itmAction; private Descriptor desc; private Attribute attr; private class WithRespectToAction implements ActionListener { public void actionPerformed(ActionEvent e) { if (attr == null) { return; } ViperViewMediator m = getMediator(); Attribute oldWRT = m.getDisplayWRTManager().getAttribute(); if (attr.equals(oldWRT)) { m.getDisplayWRTManager().setAttribute(null, null); } else { m.getDisplayWRTManager().setAttribute(attr, m.getCurrentFrame()); } } } private class ValidAction implements ActionListener { public void actionPerformed(ActionEvent e) { boolean makeValid = v.isSelected(); InstantRange oldRange = (InstantRange) desc.getValidRange().clone(); boolean frame = oldRange.isFrameBased(); InstantInterval toAlter = getMediator().getCurrInterval(frame); if (!makeValid) { oldRange.remove(toAlter); } else { oldRange.add(toAlter); } desc.setValidRange(oldRange); v.setSelected(!makeValid); } } private class PropAction implements ActionListener { public void actionPerformed(ActionEvent e) { boolean propagate = p.isSelected(); ViperViewMediator m = getMediator(); PropagateInterpolateModule proper = m.getPropagator(); if (propagate) { proper.startPropagating(desc); } else { proper.stopPropagating(desc); } p.setSelected(proper.getPropagatingDescriptors().contains(desc)); } } private class DeleteAction implements ActionListener { public void actionPerformed(ActionEvent e) { desc.getParent().removeChild(desc); } } private class DuplicateAction implements ActionListener { public void actionPerformed(ActionEvent e) { getMediator().duplicateDescriptor(desc); } } private class InterpAction implements ActionListener { public void actionPerformed(ActionEvent e) { Iterator toInterp = Collections.singleton(desc).iterator(); ViperViewMediator m = getMediator(); InterpQuery iq = new InterpQuery(toInterp, m); iq.setVisible(true); } } private class ShiftAction implements ActionListener { public void actionPerformed(ActionEvent e) { ViperViewMediator m = getMediator(); ShiftQuery sq = new ShiftQuery(new Descriptor[] {desc}, m); sq.setVisible(true); } } private class InterpToMarkAction implements ActionListener { public void actionPerformed(ActionEvent e) { Iterator toInterp = Collections.singleton(desc).iterator(); JMenuItem jmi = (JMenuItem) e.getSource(); Iterator marks = mediator.getMarkerModel().getMarkersWithLabel(jmi.getText()); if (marks.hasNext()) { ChronicleMarker marker = (ChronicleMarker) marks.next(); Instant to = marker.getWhen(); Instant from = mediator.getMajorMoment(); mediator.getPropagator().interpolateDescriptors(toInterp, from, to); } } } private class ShiftToMarkAction implements ActionListener { public void actionPerformed(ActionEvent e) { JMenuItem jmi = (JMenuItem) e.getSource(); Iterator marks = mediator.getMarkerModel().getMarkersWithLabel(jmi.getText()); if (marks.hasNext()) { ChronicleMarker marker = (ChronicleMarker) marks.next(); Instant to = marker.getWhen(); Instant from = mediator.getMajorMoment(); viper.api.impl.Util.shiftDescriptors(new Descriptor[] {desc}, from, to); } } } private JMenuItem occlusions; private TextlineOcclusionEditor occWindow = new TextlineOcclusionEditor(); private class OccAction implements ActionListener { public void actionPerformed(ActionEvent e) { ViperViewMediator med = getMediator(); TextlineModel tlm = (TextlineModel) med.getAttributeValueAtCurrentFrame(attr); if (tlm != null) { occWindow.setVisible(true); occWindow.setModelAndRefresh(tlm, med, attr); } } } private OccAction occAction; private JSeparator occSeparator; public DescPropPopup() { super("Descriptor Properties"); v = new JCheckBoxMenuItem("Valid"); v.addActionListener(new ValidAction()); p = new JCheckBoxMenuItem("Propagating"); p.addActionListener(new PropAction()); delete = new JMenuItem("Delete"); delete.addActionListener(new DeleteAction()); duplicate = new JMenuItem("Duplicate"); duplicate.addActionListener(new DuplicateAction()); interp = new JMenuItem("Interpolate..."); interp.addActionListener(new InterpAction()); interpToMark = new JMenu("Interpolate to Mark"); interpToMark.setEnabled(false); itmAction = new InterpToMarkAction(); shift = new JMenuItem("Shift..."); shift.addActionListener(new ShiftAction()); shiftToMark = new JMenu("Shift to Mark"); shiftToMark.setEnabled(false); stmAction = new ShiftToMarkAction(); occlusions = new JMenuItem("Occlusions..."); occAction = new OccAction(); occlusions.addActionListener(occAction); occSeparator = new JSeparator(); wrt = new JCheckBoxMenuItem("Display with Respect To", false); wrt.addActionListener(new WithRespectToAction()); add(occlusions); add(occSeparator); add(v); add(p); add(occSeparator); add(delete); add(duplicate); add(occSeparator); add(interp); add(interpToMark); add(occSeparator); add(shift); add(shiftToMark); add(occSeparator); add(wrt); } public void show(Component invoker, int x, int y) { ViperViewMediator mediator = getMediator(); Point pnt = new Point(x, y); EnhancedTable tab = getTable(); int row = tab.rowAtPoint(pnt); desc = getCurrentModel().getDescriptorAtRow(row); int col = tab.columnAtPoint(pnt); Object cellValue = tab.getValueAt(row, col); if (cellValue instanceof Attribute) { attr = (Attribute) cellValue; // hide the "Occlusions..." option when we're not dealing with a Textline object boolean isTextline = attr.getAttrConfig().getAttrType().endsWith("textline"); occlusions.setVisible(isTextline); occSeparator.setVisible(isTextline); Instant now = mediator.getCurrentFrame(); if (now == null) { mediator.getDisplayWRTManager().setAttribute(null, null); wrt.setEnabled(false); wrt.setSelected(false); } else { boolean isDwrt = attr == mediator.getDisplayWRTManager().getAttribute(); boolean dwrtable = (attr.getAttrValueAtInstant(now) instanceof HasCentroid && attr.getDescriptor().getValidRange().contains(now)); wrt.setEnabled(dwrtable); wrt.setSelected(isDwrt); } } else { attr = null; wrt.setEnabled(false); wrt.setSelected(false); } if (null != desc) { PropagateInterpolateModule proper = getMediator().getPropagator(); p.setSelected(proper.isPropagatingThis(desc)); v.setSelected(mediator.isThisValidNow(desc)); resetMarks(); super.show(invoker, x, y); } } private void resetMarks() { interpToMark.removeAll(); shiftToMark.removeAll(); Iterator marks = mediator.getMarkerModel().getLabels().iterator(); boolean hasMark = false; while (marks.hasNext()) { String mark = (String) marks.next(); if (!ChronicleViewer.CURR_FRAME_LABEL.equals(mark)) { JMenuItem mi = new JMenuItem(mark); mi.addActionListener(itmAction); interpToMark.add(mi); mi = new JMenuItem(mark); mi.addActionListener(stmAction); shiftToMark.add(mi); hasMark = true; } } shiftToMark.setEnabled(hasMark); interpToMark.setEnabled(hasMark); } } public abstract void redoSelectionModel(); public abstract void redoDataModel(); public abstract Config getConfig(); public void redoPropagateModel() { ViperTableModel m = (ViperTableModel) AbstractViperTable.this.getTable().getModel(); m.fireTableDataChanged(); } }
/** * A property sheet, allowing the user to edit different properties. The default implementation uses * the javabean standard naming conventions to extract property names and types. The user/system can * override this with the application preferences. */ public class PropertySheet extends JScrollPane { private DescriberBasedProperties props; private Logger logger = Logger.getLogger("edu.umd.cfar.lamp.apploader.propertysheets"); /** * Gets the data model associated with the property sheet. * * @return the table model */ private MyTableModel getTableModel() { return (MyTableModel) getTable().getModel(); } /** * Gets the table contained within the property sheet. I'm not sure that this is the best way to * present properties, and may stop using tables in the future. * * @return the table */ public EnhancedTable getTable() { return (EnhancedTable) getViewport().getView(); } private class MyTableModel extends AbstractTableModel { /** @return 2 */ public int getColumnCount() { return 2; } /** @return the number of properties */ public int getRowCount() { return props.size(); } /** * Gets the property descriptor for the row. * * @param r the row number (0-indexed) * @return the corresponding property */ public InstancePropertyDescriptor getRow(int r) { return (InstancePropertyDescriptor) props.get(r); } /** * Determines if the value at a cell is editable * * @param rowIndex the property * @param columnIndex whether the property name (column zero) or property value (column one) * @return true, if the corresponding property is settable and the column index is 1 */ public boolean isCellEditable(int rowIndex, int columnIndex) { InstancePropertyDescriptor row = getRow(rowIndex); if (columnIndex == 0) { return false; } else if (columnIndex == 1) { return row.isSettable(props.getObject()); } else { throw new IndexOutOfBoundsException("Not a valid cell: " + rowIndex + "x" + columnIndex); } } /** * Gets the appropriate property name or property value. * * @param rowIndex the row corresponding to a property * @param columnIndex either the name column (zero) or value column (one) * @return the name or value of the appropriate property */ public Object getValueAt(int rowIndex, int columnIndex) { InstancePropertyDescriptor row = getRow(rowIndex); if (columnIndex == 0) { return row.getName(); } else if (columnIndex == 1) { return row; } else { throw new IndexOutOfBoundsException("Not a valid cell: " + rowIndex + "x" + columnIndex); } } /** * Either "property" or "value". XXX: this should be localizable. * * @param columnIndex either the name column (zero) or value column (one) * @return "property" or "value" */ public String getColumnName(int columnIndex) { if (columnIndex == 0) { return "Property"; } else if (columnIndex == 1) { return "Value"; } else { throw new IndexOutOfBoundsException("Not a valid column: " + columnIndex); } } /** * Class corresponding to the column * * @param columnIndex either the name column (zero) or value column (one) * @return <code>String.class</code> or <code>{@link InstancePropertyDescriptor}.class</code> */ public Class getColumnClass(int columnIndex) { if (columnIndex == 0) { return String.class; } else if (columnIndex == 1) { return InstancePropertyDescriptor.class; } else { throw new IndexOutOfBoundsException("Not a valid column: " + columnIndex); } } /** * Sets the appropriate property value, if possible. Otherwise, this silently fails. * * @param value the new value for the appropriate property * @param rowIndex the row corresponding to a property * @param columnIndex only the value column (column one) works */ public void setValueAt(Object value, int rowIndex, int columnIndex) { if (columnIndex == 1) { InstancePropertyDescriptor row = getRow(rowIndex); if (row.isSettable(props.getObject())) { row.applySetter(props.getObject(), value); } } } /** * Gets the primary property table corresponding to this model. * * @return the table */ public EnhancedTable getTable() { return (EnhancedTable) getViewport().getView(); } } /** Create a new, empty property sheet. */ public PropertySheet() { super(new EnhancedTable()); this.getPreferredSize(); props = new ForClassPropertyList(); // Add bean change event listener props.addChangeListener( new ChangeListener() { public void stateChanged(ChangeEvent e) { // XXX Should I fire a 'stop editing' event here? refresh(); } }); // Add table JTable table = getTable(); MyTableModel tableModel = new MyTableModel(); table.setModel(tableModel); table.setDefaultRenderer(InstancePropertyDescriptor.class, new MyCellRenderer()); table.setDefaultEditor(InstancePropertyDescriptor.class, new MyCellEditor()); } private class MyCellRenderer implements TableCellRenderer { private JComponent lastEditor; /** * @see javax.swing.table.TableCellRenderer#getTableCellRendererComponent(javax.swing.JTable, * java.lang.Object, boolean, boolean, int, int) */ public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { InstancePropertyDescriptor v = (InstancePropertyDescriptor) value; try { lastEditor = v.getRenderer(props.getObject(), props.getPrefs().getCore()); return lastEditor; } catch (PreferenceException e) { e.printStackTrace(); return null; } } } private class MyCellEditor extends AbstractCellEditor implements TableCellEditor { private CellEditor lastEditor; /** @see javax.swing.CellEditor#getCellEditorValue() */ public Object getCellEditorValue() { if (lastEditor != null) { return lastEditor.getCellEditorValue(); } return null; } /** * @see javax.swing.table.TableCellEditor#getTableCellEditorComponent(javax.swing.JTable, * java.lang.Object, boolean, int, int) */ public Component getTableCellEditorComponent( JTable table, Object value, boolean isSelected, int row, int column) { InstancePropertyDescriptor v = (InstancePropertyDescriptor) value; JComponent c; try { c = v.getEditor(props.getObject(), props.getPrefs().getCore()); lastEditor = (CellEditor) c; return c; } catch (PreferenceException e) { e.printStackTrace(); return null; } } } /** * Gets the preference manager associated with this property sheet. * * @return the preference manager */ public PrefsManager getPrefs() { return props.getPrefs(); } /** * Sets the preference manager associated with this property sheet. This is necessary for * localization and determining extended properties. * * @param manager the preference manager */ public void setPrefs(PrefsManager manager) { props.setPrefs(manager); } /** Sort the descriptors by their display name using the current lexicographical ordering. */ private static class PropertySorter implements Comparator { /** * Sort the descriptors by their display name using the current lexicographical ordering * * @param a a property descriptor * @param b the other ObjectPropertyDescriptor * @return <code>getName().compareto(b.getName())</code> */ public int compare(Object a, Object b) { InstancePropertyDescriptor A = (InstancePropertyDescriptor) a; InstancePropertyDescriptor B = (InstancePropertyDescriptor) b; return A.getName().compareTo(B.getName()); } } static final PropertySorter SORT_BY_PROPERTY_NAME = new PropertySorter(); /** * Set the subject bean to check for properties. * * @param o the subject bean */ public void setObject(Object o) { if (o != props.getObject()) { if (getTable().isEditing()) { getTable().editingStopped(new ChangeEvent(this)); } getTable().editingCanceled(new ChangeEvent(this)); props.setObject(o); getTableModel().fireTableStructureChanged(); getTable().setRowHeightToMaximumPreferredHeight(); } } /** * Gets the bound subject bean. * * @return the subject */ public Object getObject() { return props.getObject(); } /** Refresh the properties from the bound object. */ public void refresh() { props.refresh(); getTableModel().fireTableDataChanged(); } /** @return */ public Logger getLogger() { return logger; } /** @param logger */ public void setLogger(Logger logger) { this.logger = logger; } }