Ejemplo n.º 1
0
public class MyFileListTable extends MyDefaultTable {
  private static Logger log = Logger.getLogger(MyFileListTable.class.getName());

  public MyFileListTable() {
    super();
  }

  public MyFileListTable(TableModel dm) {
    super(dm);
  }

  protected void init() {
    super.init();
    setRowHeight(16);
    setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    enableAutoPack(true);
  }

  public TableCellRenderer getCellRenderer(int row, int column) {
    // log.info(row+", "+column);
    return super.getCellRenderer(row, column);
  }

  protected void resizeTable() {
    if (getColumnCount() > 1) {
      packColumn(this, 0);
      packColumn(this, 1);
    }
  }

  /** Overrides <code>JComponent</code>'s <code>getToolTipText</code> */
  public String getToolTipText(MouseEvent e) {
    String tip = null;
    java.awt.Point p = e.getPoint();
    int rowIndex = rowAtPoint(p);
    // int colIndex = columnAtPoint(p);
    // int realColumnIndex = convertColumnIndexToModel(colIndex);
    TableModel model = getModel();
    tip = (String) model.getValueAt(rowIndex, 1);
    return tip;
  }
}
Ejemplo n.º 2
0
/**
 * @author Lyubomir Marinov
 * @author Damian Minkov
 * @author Yana Stamcheva
 */
public class MediaConfiguration {
  /** The <tt>Logger</tt> used by the <tt>MediaConfiguration</tt> class for logging output. */
  private static final Logger logger = Logger.getLogger(MediaConfiguration.class);

  /** The <tt>MediaService</tt> implementation used by <tt>MediaConfiguration</tt>. */
  private static final MediaServiceImpl mediaService = NeomediaActivator.getMediaServiceImpl();

  /** The preferred width of all panels. */
  private static final int WIDTH = 350;

  /**
   * Indicates if the Devices settings configuration tab should be disabled, i.e. not visible to the
   * user.
   */
  private static final String DEVICES_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.devicesconfig.DISABLED";

  /**
   * Indicates if the Audio/Video encodings configuration tab should be disabled, i.e. not visible
   * to the user.
   */
  private static final String ENCODINGS_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.encodingsconfig.DISABLED";

  /**
   * Indicates if the Video/More Settings configuration tab should be disabled, i.e. not visible to
   * the user.
   */
  private static final String VIDEO_MORE_SETTINGS_DISABLED_PROP =
      "net.java.sip.communicator.impl.neomedia.videomoresettingsconfig.DISABLED";

  /**
   * Returns the audio configuration panel.
   *
   * @return the audio configuration panel
   */
  public static Component createAudioConfigPanel() {
    return createControls(DeviceConfigurationComboBoxModel.AUDIO);
  }

  /**
   * Returns the video configuration panel.
   *
   * @return the video configuration panel
   */
  public static Component createVideoConfigPanel() {
    return createControls(DeviceConfigurationComboBoxModel.VIDEO);
  }

  private static void createAudioPreview(
      final AudioSystem audioSystem,
      final JComboBox comboBox,
      final SoundLevelIndicator soundLevelIndicator) {
    final ActionListener captureComboActionListener =
        new ActionListener() {
          private final SimpleAudioLevelListener audioLevelListener =
              new SimpleAudioLevelListener() {
                public void audioLevelChanged(int level) {
                  soundLevelIndicator.updateSoundLevel(level);
                }
              };

          private AudioMediaDeviceSession deviceSession;

          private final BufferTransferHandler transferHandler =
              new BufferTransferHandler() {
                public void transferData(PushBufferStream stream) {
                  try {
                    stream.read(transferHandlerBuffer);
                  } catch (IOException ioe) {
                  }
                }
              };

          private final Buffer transferHandlerBuffer = new Buffer();

          public void actionPerformed(ActionEvent event) {
            setDeviceSession(null);

            CaptureDeviceInfo cdi;

            if (comboBox == null) {
              cdi = soundLevelIndicator.isShowing() ? audioSystem.getCaptureDevice() : null;
            } else {
              Object selectedItem =
                  soundLevelIndicator.isShowing() ? comboBox.getSelectedItem() : null;

              cdi =
                  (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice)
                      ? ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info
                      : null;
            }

            if (cdi != null) {
              for (MediaDevice md : mediaService.getDevices(MediaType.AUDIO, MediaUseCase.ANY)) {
                if (md instanceof AudioMediaDeviceImpl) {
                  AudioMediaDeviceImpl amd = (AudioMediaDeviceImpl) md;

                  if (cdi.equals(amd.getCaptureDeviceInfo())) {
                    try {
                      MediaDeviceSession deviceSession = amd.createSession();
                      boolean setDeviceSession = false;

                      try {
                        if (deviceSession instanceof AudioMediaDeviceSession) {
                          setDeviceSession((AudioMediaDeviceSession) deviceSession);
                          setDeviceSession = true;
                        }
                      } finally {
                        if (!setDeviceSession) deviceSession.close();
                      }
                    } catch (Throwable t) {
                      if (t instanceof ThreadDeath) throw (ThreadDeath) t;
                    }
                    break;
                  }
                }
              }
            }
          }

          private void setDeviceSession(AudioMediaDeviceSession deviceSession) {
            if (this.deviceSession == deviceSession) return;

            if (this.deviceSession != null) {
              try {
                this.deviceSession.close();
              } finally {
                this.deviceSession.setLocalUserAudioLevelListener(null);
                soundLevelIndicator.resetSoundLevel();
              }
            }

            this.deviceSession = deviceSession;

            if (this.deviceSession != null) {
              this.deviceSession.setContentDescriptor(new ContentDescriptor(ContentDescriptor.RAW));
              this.deviceSession.setLocalUserAudioLevelListener(audioLevelListener);
              this.deviceSession.start(MediaDirection.SENDONLY);

              try {
                DataSource dataSource = this.deviceSession.getOutputDataSource();

                dataSource.connect();

                PushBufferStream[] streams = ((PushBufferDataSource) dataSource).getStreams();

                for (PushBufferStream stream : streams) stream.setTransferHandler(transferHandler);

                dataSource.start();
              } catch (Throwable t) {
                if (t instanceof ThreadDeath) throw (ThreadDeath) t;
                else setDeviceSession(null);
              }
            }
          }
        };

    if (comboBox != null) comboBox.addActionListener(captureComboActionListener);

    soundLevelIndicator.addHierarchyListener(
        new HierarchyListener() {
          public void hierarchyChanged(HierarchyEvent event) {
            if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
              SwingUtilities.invokeLater(
                  new Runnable() {
                    public void run() {
                      captureComboActionListener.actionPerformed(null);
                    }
                  });
            }
          }
        });
  }
  /**
   * Creates the UI controls which are to control the details of a specific <tt>AudioSystem</tt>.
   *
   * @param audioSystem the <tt>AudioSystem</tt> for which the UI controls to control its details
   *     are to be created
   * @param container the <tt>JComponent</tt> into which the UI controls which are to control the
   *     details of the specified <tt>audioSystem</tt> are to be added
   */
  public static void createAudioSystemControls(AudioSystem audioSystem, JComponent container) {
    GridBagConstraints constraints = new GridBagConstraints();

    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.weighty = 0;

    int audioSystemFeatures = audioSystem.getFeatures();
    boolean featureNotifyAndPlaybackDevices =
        ((audioSystemFeatures & AudioSystem.FEATURE_NOTIFY_AND_PLAYBACK_DEVICES) != 0);

    constraints.gridx = 0;
    constraints.insets = new Insets(3, 0, 3, 3);
    constraints.weightx = 0;

    constraints.gridy = 0;
    container.add(
        new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_CAPTURE)), constraints);
    if (featureNotifyAndPlaybackDevices) {
      constraints.gridy = 2;
      container.add(
          new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK)), constraints);
      constraints.gridy = 3;
      container.add(
          new JLabel(getLabelText(DeviceConfigurationComboBoxModel.AUDIO_NOTIFY)), constraints);
    }

    constraints.gridx = 1;
    constraints.insets = new Insets(3, 3, 3, 0);
    constraints.weightx = 1;

    JComboBox captureCombo = null;

    if (featureNotifyAndPlaybackDevices) {
      captureCombo = new JComboBox();
      captureCombo.setEditable(false);
      captureCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_CAPTURE));
      constraints.gridy = 0;
      container.add(captureCombo, constraints);
    }

    int anchor = constraints.anchor;
    SoundLevelIndicator capturePreview =
        new SoundLevelIndicator(
            SimpleAudioLevelListener.MIN_LEVEL, SimpleAudioLevelListener.MAX_LEVEL);

    constraints.anchor = GridBagConstraints.CENTER;
    constraints.gridy = (captureCombo == null) ? 0 : 1;
    container.add(capturePreview, constraints);
    constraints.anchor = anchor;

    constraints.gridy = GridBagConstraints.RELATIVE;

    if (featureNotifyAndPlaybackDevices) {
      JComboBox playbackCombo = new JComboBox();

      playbackCombo.setEditable(false);
      playbackCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK));
      container.add(playbackCombo, constraints);

      JComboBox notifyCombo = new JComboBox();

      notifyCombo.setEditable(false);
      notifyCombo.setModel(
          new DeviceConfigurationComboBoxModel(
              captureCombo,
              mediaService.getDeviceConfiguration(),
              DeviceConfigurationComboBoxModel.AUDIO_NOTIFY));
      container.add(notifyCombo, constraints);
    }

    if ((AudioSystem.FEATURE_ECHO_CANCELLATION & audioSystemFeatures) != 0) {
      final SIPCommCheckBox echoCancelCheckBox =
          new SIPCommCheckBox(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.ECHOCANCEL"));

      /*
       * First set the selected one, then add the listener in order to
       * avoid saving the value when using the default one and only
       * showing to user without modification.
       */
      echoCancelCheckBox.setSelected(mediaService.getDeviceConfiguration().isEchoCancel());
      echoCancelCheckBox.addItemListener(
          new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
              mediaService.getDeviceConfiguration().setEchoCancel(echoCancelCheckBox.isSelected());
            }
          });
      container.add(echoCancelCheckBox, constraints);
    }

    if ((AudioSystem.FEATURE_DENOISE & audioSystemFeatures) != 0) {
      final SIPCommCheckBox denoiseCheckBox =
          new SIPCommCheckBox(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.DENOISE"));

      /*
       * First set the selected one, then add the listener in order to
       * avoid saving the value when using the default one and only
       * showing to user without modification.
       */
      denoiseCheckBox.setSelected(mediaService.getDeviceConfiguration().isDenoise());
      denoiseCheckBox.addItemListener(
          new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
              mediaService.getDeviceConfiguration().setDenoise(denoiseCheckBox.isSelected());
            }
          });
      container.add(denoiseCheckBox, constraints);
    }

    createAudioPreview(audioSystem, captureCombo, capturePreview);
  }

  /**
   * Creates basic controls for a type (AUDIO or VIDEO).
   *
   * @param type the type.
   * @return the build Component.
   */
  public static Component createBasicControls(final int type) {
    final JComboBox deviceComboBox = new JComboBox();

    deviceComboBox.setEditable(false);
    deviceComboBox.setModel(
        new DeviceConfigurationComboBoxModel(
            deviceComboBox, mediaService.getDeviceConfiguration(), type));

    JLabel deviceLabel = new JLabel(getLabelText(type));

    deviceLabel.setDisplayedMnemonic(getDisplayedMnemonic(type));
    deviceLabel.setLabelFor(deviceComboBox);

    final Container devicePanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));

    devicePanel.setMaximumSize(new Dimension(WIDTH, 25));
    devicePanel.add(deviceLabel);
    devicePanel.add(deviceComboBox);

    final JPanel deviceAndPreviewPanel = new TransparentPanel(new BorderLayout());
    int preferredDeviceAndPreviewPanelHeight;

    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        preferredDeviceAndPreviewPanelHeight = 225;
        break;
      case DeviceConfigurationComboBoxModel.VIDEO:
        preferredDeviceAndPreviewPanelHeight = 305;
        break;
      default:
        preferredDeviceAndPreviewPanelHeight = 0;
        break;
    }
    if (preferredDeviceAndPreviewPanelHeight > 0)
      deviceAndPreviewPanel.setPreferredSize(
          new Dimension(WIDTH, preferredDeviceAndPreviewPanelHeight));
    deviceAndPreviewPanel.add(devicePanel, BorderLayout.NORTH);

    final ActionListener deviceComboBoxActionListener =
        new ActionListener() {
          public void actionPerformed(ActionEvent event) {
            boolean revalidateAndRepaint = false;

            for (int i = deviceAndPreviewPanel.getComponentCount() - 1; i >= 0; i--) {
              Component c = deviceAndPreviewPanel.getComponent(i);

              if (c != devicePanel) {
                deviceAndPreviewPanel.remove(i);
                revalidateAndRepaint = true;
              }
            }

            Component preview = null;

            if ((deviceComboBox.getSelectedItem() != null) && deviceComboBox.isShowing()) {
              preview =
                  createPreview(type, deviceComboBox, deviceAndPreviewPanel.getPreferredSize());
            }

            if (preview != null) {
              deviceAndPreviewPanel.add(preview, BorderLayout.CENTER);
              revalidateAndRepaint = true;
            }

            if (revalidateAndRepaint) {
              deviceAndPreviewPanel.revalidate();
              deviceAndPreviewPanel.repaint();
            }
          }
        };

    deviceComboBox.addActionListener(deviceComboBoxActionListener);
    /*
     * We have to initialize the controls to reflect the configuration
     * at the time of creating this instance. Additionally, because the
     * video preview will stop when it and its associated controls
     * become unnecessary, we have to restart it when the mentioned
     * controls become necessary again. We'll address the two goals
     * described by pretending there's a selection in the video combo
     * box when the combo box in question becomes displayable.
     */
    deviceComboBox.addHierarchyListener(
        new HierarchyListener() {
          public void hierarchyChanged(HierarchyEvent event) {
            if ((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
              SwingUtilities.invokeLater(
                  new Runnable() {
                    public void run() {
                      deviceComboBoxActionListener.actionPerformed(null);
                    }
                  });
            }
          }
        });

    return deviceAndPreviewPanel;
  }

  /**
   * Creates all the controls (including encoding) for a type(AUDIO or VIDEO)
   *
   * @param type the type.
   * @return the build Component.
   */
  private static Component createControls(int type) {
    ConfigurationService cfg = NeomediaActivator.getConfigurationService();
    SIPCommTabbedPane container = new SIPCommTabbedPane();
    ResourceManagementService res = NeomediaActivator.getResources();

    if ((cfg == null) || !cfg.getBoolean(DEVICES_DISABLED_PROP, false)) {
      container.insertTab(
          res.getI18NString("impl.media.configform.DEVICES"),
          null,
          createBasicControls(type),
          null,
          0);
    }
    if ((cfg == null) || !cfg.getBoolean(ENCODINGS_DISABLED_PROP, false)) {
      container.insertTab(
          res.getI18NString("impl.media.configform.ENCODINGS"),
          null,
          new PriorityTable(
              new EncodingConfigurationTableModel(mediaService.getEncodingConfiguration(), type),
              100),
          null,
          1);
    }
    if ((type == DeviceConfigurationComboBoxModel.VIDEO)
        && ((cfg == null) || !cfg.getBoolean(VIDEO_MORE_SETTINGS_DISABLED_PROP, false))) {
      container.insertTab(
          res.getI18NString("impl.media.configform.VIDEO_MORE_SETTINGS"),
          null,
          createVideoAdvancedSettings(),
          null,
          2);
    }
    return container;
  }

  /**
   * Creates preview for the (video) device in the video container.
   *
   * @param device the device
   * @param videoContainer the video container
   * @throws IOException a problem accessing the device
   * @throws MediaException a problem getting preview
   */
  private static void createVideoPreview(CaptureDeviceInfo device, JComponent videoContainer)
      throws IOException, MediaException {
    videoContainer.removeAll();

    videoContainer.revalidate();
    videoContainer.repaint();

    if (device == null) return;

    for (MediaDevice mediaDevice : mediaService.getDevices(MediaType.VIDEO, MediaUseCase.ANY)) {
      if (((MediaDeviceImpl) mediaDevice).getCaptureDeviceInfo().equals(device)) {
        Dimension videoContainerSize = videoContainer.getPreferredSize();
        Component preview =
            (Component)
                mediaService.getVideoPreviewComponent(
                    mediaDevice, videoContainerSize.width, videoContainerSize.height);

        if (preview != null) videoContainer.add(preview);
        break;
      }
    }
  }

  /**
   * Create preview component.
   *
   * @param type type
   * @param comboBox the options.
   * @param prefSize the preferred size
   * @return the component.
   */
  private static Component createPreview(int type, final JComboBox comboBox, Dimension prefSize) {
    JComponent preview = null;

    if (type == DeviceConfigurationComboBoxModel.AUDIO) {
      Object selectedItem = comboBox.getSelectedItem();

      if (selectedItem instanceof AudioSystem) {
        AudioSystem audioSystem = (AudioSystem) selectedItem;

        if (!NoneAudioSystem.LOCATOR_PROTOCOL.equalsIgnoreCase(audioSystem.getLocatorProtocol())) {
          preview = new TransparentPanel(new GridBagLayout());
          createAudioSystemControls(audioSystem, preview);
        }
      }
    } else if (type == DeviceConfigurationComboBoxModel.VIDEO) {
      JLabel noPreview =
          new JLabel(
              NeomediaActivator.getResources().getI18NString("impl.media.configform.NO_PREVIEW"));

      noPreview.setHorizontalAlignment(SwingConstants.CENTER);
      noPreview.setVerticalAlignment(SwingConstants.CENTER);

      preview = createVideoContainer(noPreview);
      preview.setPreferredSize(prefSize);

      Object selectedItem = comboBox.getSelectedItem();
      CaptureDeviceInfo device = null;
      if (selectedItem instanceof DeviceConfigurationComboBoxModel.CaptureDevice)
        device = ((DeviceConfigurationComboBoxModel.CaptureDevice) selectedItem).info;

      Exception exception;
      try {
        createVideoPreview(device, preview);
        exception = null;
      } catch (IOException ex) {
        exception = ex;
      } catch (MediaException ex) {
        exception = ex;
      }
      if (exception != null) {
        logger.error("Failed to create preview for device " + device, exception);
        device = null;
      }
    }

    return preview;
  }

  /**
   * Creates the video container.
   *
   * @param noVideoComponent the container component.
   * @return the video container.
   */
  private static JComponent createVideoContainer(Component noVideoComponent) {
    return new VideoContainer(noVideoComponent, false);
  }

  /**
   * The mnemonic for a type.
   *
   * @param type audio or video type.
   * @return the mnemonic.
   */
  private static char getDisplayedMnemonic(int type) {
    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.AUDIO");
      case DeviceConfigurationComboBoxModel.VIDEO:
        return NeomediaActivator.getResources().getI18nMnemonic("impl.media.configform.VIDEO");
      default:
        throw new IllegalArgumentException("type");
    }
  }

  /**
   * A label for a type.
   *
   * @param type the type.
   * @return the label.
   */
  private static String getLabelText(int type) {
    switch (type) {
      case DeviceConfigurationComboBoxModel.AUDIO:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO");
      case DeviceConfigurationComboBoxModel.AUDIO_CAPTURE:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_IN");
      case DeviceConfigurationComboBoxModel.AUDIO_NOTIFY:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_NOTIFY");
      case DeviceConfigurationComboBoxModel.AUDIO_PLAYBACK:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.AUDIO_OUT");
      case DeviceConfigurationComboBoxModel.VIDEO:
        return NeomediaActivator.getResources().getI18NString("impl.media.configform.VIDEO");
      default:
        throw new IllegalArgumentException("type");
    }
  }

  /**
   * Creates the video advanced settings.
   *
   * @return video advanced settings panel.
   */
  private static Component createVideoAdvancedSettings() {
    ResourceManagementService resources = NeomediaActivator.getResources();

    final DeviceConfiguration deviceConfig = mediaService.getDeviceConfiguration();

    TransparentPanel centerPanel = new TransparentPanel(new GridBagLayout());
    centerPanel.setMaximumSize(new Dimension(WIDTH, 150));

    JButton resetDefaultsButton =
        new JButton(resources.getI18NString("impl.media.configform.VIDEO_RESET"));
    JPanel resetButtonPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));
    resetButtonPanel.add(resetDefaultsButton);

    final JPanel centerAdvancedPanel = new TransparentPanel(new BorderLayout());
    centerAdvancedPanel.add(centerPanel, BorderLayout.NORTH);
    centerAdvancedPanel.add(resetButtonPanel, BorderLayout.SOUTH);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 0, 0);
    constraints.gridx = 0;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.gridy = 0;

    centerPanel.add(
        new JLabel(resources.getI18NString("impl.media.configform.VIDEO_RESOLUTION")), constraints);
    constraints.gridy = 1;
    constraints.insets = new Insets(0, 0, 0, 0);
    final JCheckBox frameRateCheck =
        new SIPCommCheckBox(resources.getI18NString("impl.media.configform.VIDEO_FRAME_RATE"));
    centerPanel.add(frameRateCheck, constraints);
    constraints.gridy = 2;
    constraints.insets = new Insets(5, 5, 0, 0);
    centerPanel.add(
        new JLabel(resources.getI18NString("impl.media.configform.VIDEO_PACKETS_POLICY")),
        constraints);

    constraints.weightx = 1;
    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.insets = new Insets(5, 0, 0, 5);
    Object[] resolutionValues = new Object[DeviceConfiguration.SUPPORTED_RESOLUTIONS.length + 1];
    System.arraycopy(
        DeviceConfiguration.SUPPORTED_RESOLUTIONS,
        0,
        resolutionValues,
        1,
        DeviceConfiguration.SUPPORTED_RESOLUTIONS.length);
    final JComboBox sizeCombo = new JComboBox(resolutionValues);
    sizeCombo.setRenderer(new ResolutionCellRenderer());
    sizeCombo.setEditable(false);
    centerPanel.add(sizeCombo, constraints);

    // default value is 20
    final JSpinner frameRate = new JSpinner(new SpinnerNumberModel(20, 5, 30, 1));
    frameRate.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            deviceConfig.setFrameRate(
                ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue());
          }
        });
    constraints.gridy = 1;
    constraints.insets = new Insets(0, 0, 0, 5);
    centerPanel.add(frameRate, constraints);

    frameRateCheck.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            if (frameRateCheck.isSelected()) {
              deviceConfig.setFrameRate(
                  ((SpinnerNumberModel) frameRate.getModel()).getNumber().intValue());
            } else // unlimited framerate
            deviceConfig.setFrameRate(-1);

            frameRate.setEnabled(frameRateCheck.isSelected());
          }
        });

    final JSpinner videoMaxBandwidth =
        new JSpinner(
            new SpinnerNumberModel(deviceConfig.getVideoMaxBandwidth(), 1, Integer.MAX_VALUE, 1));
    videoMaxBandwidth.addChangeListener(
        new ChangeListener() {
          public void stateChanged(ChangeEvent e) {
            deviceConfig.setVideoMaxBandwidth(
                ((SpinnerNumberModel) videoMaxBandwidth.getModel()).getNumber().intValue());
          }
        });
    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.insets = new Insets(0, 0, 5, 5);
    centerPanel.add(videoMaxBandwidth, constraints);

    resetDefaultsButton.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            // reset to defaults
            sizeCombo.setSelectedIndex(0);
            frameRateCheck.setSelected(false);
            frameRate.setEnabled(false);
            frameRate.setValue(20);
            // unlimited framerate
            deviceConfig.setFrameRate(-1);
            videoMaxBandwidth.setValue(DeviceConfiguration.DEFAULT_VIDEO_MAX_BANDWIDTH);
          }
        });

    // load selected value or auto
    Dimension videoSize = deviceConfig.getVideoSize();

    if ((videoSize.getHeight() != DeviceConfiguration.DEFAULT_VIDEO_HEIGHT)
        && (videoSize.getWidth() != DeviceConfiguration.DEFAULT_VIDEO_WIDTH))
      sizeCombo.setSelectedItem(deviceConfig.getVideoSize());
    else sizeCombo.setSelectedIndex(0);
    sizeCombo.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            Dimension selectedVideoSize = (Dimension) sizeCombo.getSelectedItem();

            if (selectedVideoSize == null) {
              // the auto value, default one
              selectedVideoSize =
                  new Dimension(
                      DeviceConfiguration.DEFAULT_VIDEO_WIDTH,
                      DeviceConfiguration.DEFAULT_VIDEO_HEIGHT);
            }
            deviceConfig.setVideoSize(selectedVideoSize);
          }
        });

    frameRateCheck.setSelected(
        deviceConfig.getFrameRate() != DeviceConfiguration.DEFAULT_VIDEO_FRAMERATE);
    frameRate.setEnabled(frameRateCheck.isSelected());

    if (frameRate.isEnabled()) frameRate.setValue(deviceConfig.getFrameRate());

    return centerAdvancedPanel;
  }

  /** Renders the available resolutions in the combo box. */
  private static class ResolutionCellRenderer extends DefaultListCellRenderer {
    /**
     * The serialization version number of the <tt>ResolutionCellRenderer</tt> class. Defined to the
     * value of <tt>0</tt> because the <tt>ResolutionCellRenderer</tt> instances do not have state
     * of their own.
     */
    private static final long serialVersionUID = 0L;

    /**
     * Sets readable text describing the resolution if the selected value is null we return the
     * string "Auto".
     *
     * @param list
     * @param value
     * @param index
     * @param isSelected
     * @param cellHasFocus
     * @return Component
     */
    @Override
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      // call super to set backgrounds and fonts
      super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);

      // now just change the text
      if (value == null) setText("Auto");
      else if (value instanceof Dimension) {
        Dimension d = (Dimension) value;

        setText(((int) d.getWidth()) + "x" + ((int) d.getHeight()));
      }
      return this;
    }
  }
}
Ejemplo n.º 3
0
/**
 * @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();
  }
}
Ejemplo n.º 4
0
/**
 * 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;
  }
}