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