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);
  }
/**
 * @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();
  }
}