Exemple #1
0
  /**
   * Main method. Begins the GUI, and the rest of the program.
   *
   * @param args the command line arguments
   */
  public static void main(String args[]) {
    // playSound();
    try {
      for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(info.getName())) {
          UIManager.setLookAndFeel(info.getClassName());
          break;
        }
      }
    } catch (ClassNotFoundException
        | InstantiationException
        | IllegalAccessException
        | UnsupportedLookAndFeelException ex) {
      Logger.getLogger(mainForm.class.getName()).log(Level.SEVERE, null, ex);
    }
    // </editor-fold>

    /* Create and display the form */
    EventQueue.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            new mainForm().setVisible(true);
          }
        });
  }
Exemple #2
0
 public void actionPerformed(ActionEvent ae) {
   if (ae.getActionCommand() == "timer") {
     if (counter >= imagePaths.length) {
       timer.stop();
       counter = 0;
       PlayStopButton.setText("Play");
     } else {
       try {
         // if the array contains URLs
         if (imagePaths[counter].contains("http://")) {
           scrollPane.setToolTipText(imagePaths[counter]);
           imageIcon = new ImageIcon((new URL(imagePaths[counter++])));
           scrollPane.setViewportView(new JLabel(imageIcon));
         }
         // if the array contains local images
         else {
           image.setIcon(new ImageIcon(imagePaths[counter]));
           image.setToolTipText(imagePaths[counter++]);
         }
       } catch (MalformedURLException ex) {
         Logger.getLogger(Slideshow.class.getName()).log(Level.SEVERE, null, ex);
         image = new JLabel("?" + imagePaths);
       }
     }
   }
 }
  private void initLogger() {
    log = Logger.getLogger(this.getClass().getName());
    log.setUseParentHandlers(false);
    log.setLevel(Level.ALL);

    try {
      new File(LOG_FILENAME).getParentFile().mkdirs();
      StreamHandler handle =
          new StreamHandler(new FileOutputStream(LOG_FILENAME), new SimpleFormatter()) {
            @Override
            public boolean isLoggable(LogRecord record) {
              return true; // record.getLevel() == ENLevels.ERROR;
            }

            @Override
            public void publish(LogRecord record) {
              super.publish(record);
            }
          };

      log.addHandler(handle);

    } catch (IOException e) {

    }
  }
Exemple #4
0
 public void actionPerformed(ActionEvent e) {
   try {
     undo.redo();
   } catch (CannotRedoException ex) {
     Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE, "Unable to redo", ex);
   }
   update();
   undoAction.update();
 }
Exemple #5
0
    @Override
    @SuppressWarnings("SleepWhileHoldingLock")
    public void run() {
      try {
        // initialize the statusbar
        status.removeAll();
        JProgressBar progress = new JProgressBar();
        progress.setMinimum(0);
        progress.setMaximum(doc.getLength());
        status.add(progress);
        status.revalidate();

        // start writing
        Writer out = new FileWriter(f);
        Segment text = new Segment();
        text.setPartialReturn(true);
        int charsLeft = doc.getLength();
        int offset = 0;
        while (charsLeft > 0) {
          doc.getText(offset, Math.min(4096, charsLeft), text);
          out.write(text.array, text.offset, text.count);
          charsLeft -= text.count;
          offset += text.count;
          progress.setValue(offset);
          try {
            Thread.sleep(10);
          } catch (InterruptedException e) {
            Logger.getLogger(FileSaver.class.getName()).log(Level.SEVERE, null, e);
          }
        }
        out.flush();
        out.close();
      } catch (IOException e) {
        final String msg = e.getMessage();
        SwingUtilities.invokeLater(
            new Runnable() {

              public void run() {
                JOptionPane.showMessageDialog(
                    getFrame(),
                    "Could not save file: " + msg,
                    "Error saving file",
                    JOptionPane.ERROR_MESSAGE);
              }
            });
      } catch (BadLocationException e) {
        System.err.println(e.getMessage());
      }
      // we are done... get rid of progressbar
      status.removeAll();
      status.revalidate();
    }
  public BooleanToggleButtonManager(Preferences prNode, String prefName, boolean def) {
    // Chop of classname and last package component for logger.
    String p = getClass().getName();
    int idx = p.lastIndexOf('.');
    p = p.substring(0, idx);
    idx = p.lastIndexOf('.');
    p = p.substring(0, idx);
    log = Logger.getLogger(p + "." + prefName.replace('/', '.'));

    this.def = def;
    pr = prNode;
    pref = prefName;
  }
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;
  }
}
  /**
   * Indicates that the security is time-outed, is not supported by the other end.
   *
   * @param evt Details about the event that caused this message.
   */
  @Override
  public void securityTimeout(CallPeerSecurityTimeoutEvent evt) {
    timer.cancel();

    // fail peer, call
    if (evt.getSource() instanceof AbstractCallPeer) {
      try {
        CallPeer peer = (CallPeer) evt.getSource();
        OperationSetBasicTelephony<?> telephony =
            peer.getProtocolProvider().getOperationSet(OperationSetBasicTelephony.class);

        telephony.hangupCallPeer(
            peer,
            OperationSetBasicTelephony.HANGUP_REASON_ENCRYPTION_REQUIRED,
            "Encryption Required!");
      } catch (OperationFailedException ex) {
        Logger.getLogger(getClass()).error("Failed to hangup peer", ex);
      }
    }
  }
Exemple #9
0
/** @author james.bloom */
public class MainUI extends JFrame {

  /** */
  private static final long serialVersionUID = -2404746632782793567L;

  private static Logger dLog = Logger.getLogger(MainUI.class);
  private JDesktopPane theDesktop = new JDesktopPane();
  private JMenuBar menubar = new JMenuBar();

  public MainUI() {
    super("Neato Burrito");

    dLog.trace("In MainUI: " + MainUI.class.getCanonicalName());

    this.setBounds(0, 0, 500, 500);
    Container container = getContentPane();
    container.add(theDesktop);
    setJMenuBar(menubar);
    JMenu fileMenu = new JMenu("File");
    JMenuItem exitItem = new JMenuItem("Exit");
    exitItem.setMnemonic('X');
    exitItem.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            System.exit(0);
          }
        });
    fileMenu.add(exitItem);
    menubar.add(fileMenu);

    StatusUI statUI = new StatusUI();
    statUI.setVisible(true);
    theDesktop.add(statUI);

    setDefaultCloseOperation(EXIT_ON_CLOSE);
    pack();
    setVisible(true);
  }
}
Exemple #10
0
  public static Logger initLogger() {
    Logger initlogger = Logger.getLogger(settings.getMainClass());
    initlogger.setLevel(settings.getLogLevel());
    boolean addconsole = false;
    if (settings.getLogToFile()) {
      try {
        Handler handler = new FileHandler(settings.getLogFile(), true);
        handler.setFormatter(new SimpleFormatter());
        initlogger.addHandler(handler);
      } catch (Exception e) {
        Logger().warning("Could not set logfile " + settings.getLogFile() + ": " + e.getMessage());
        addconsole = true;
      }
    }
    addconsole = settings.getLogToConsole() || addconsole;
    if (addconsole) {
      initlogger.addHandler(new ConsoleHandler());
    }
    // restore original log state
    logger.setLevel(templevel);
    templevel = null;

    return initlogger;
  }
/**
 * The <tt>FileTransferConversationComponent</tt> is the parent of all file conversation components
 * - for incoming, outgoing and history file transfers.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public abstract class FileTransferConversationComponent extends ChatConversationComponent
    implements ActionListener, FileTransferProgressListener, Skinnable {
  /** The logger for this class. */
  private final Logger logger = Logger.getLogger(FileTransferConversationComponent.class);

  /** Image default width. */
  protected static final int IMAGE_WIDTH = 64;

  /** Image default height. */
  protected static final int IMAGE_HEIGHT = 64;

  /** The image label. */
  protected final FileImageLabel imageLabel;

  /** The title label. */
  protected final JLabel titleLabel = new JLabel();

  /** The file label. */
  protected final JLabel fileLabel = new JLabel();

  /** The error area. */
  private final JTextArea errorArea = new JTextArea();

  /** The error icon label. */
  private final JLabel errorIconLabel =
      new JLabel(new ImageIcon(ImageLoader.getImage(ImageLoader.EXCLAMATION_MARK)));

  /** The cancel button. */
  protected final ChatConversationButton cancelButton = new ChatConversationButton();

  /** The retry button. */
  protected final ChatConversationButton retryButton = new ChatConversationButton();

  /** The accept button. */
  protected final ChatConversationButton acceptButton = new ChatConversationButton();

  /** The reject button. */
  protected final ChatConversationButton rejectButton = new ChatConversationButton();

  /** The open file button. */
  protected final ChatConversationButton openFileButton = new ChatConversationButton();

  /** The open folder button. */
  protected final ChatConversationButton openFolderButton = new ChatConversationButton();

  /** The progress bar. */
  protected final JProgressBar progressBar = new JProgressBar();

  /** The progress properties panel. */
  private final TransparentPanel progressPropertiesPanel =
      new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

  /** The progress speed label. */
  private final JLabel progressSpeedLabel = new JLabel();

  /** The estimated time label. */
  private final JLabel estimatedTimeLabel = new JLabel();

  /** The download file. */
  private File downloadFile;

  /** The file transfer. */
  private FileTransfer fileTransfer;

  /** The speed calculated delay. */
  private static final int SPEED_CALCULATE_DELAY = 5000;

  /** The transferred file size. */
  private long transferredFileSize = 0;

  /** The time of the last calculated transfer speed. */
  private long lastSpeedTimestamp = 0;

  /** The last estimated time for the transfer. */
  private long lastEstimatedTimeTimestamp = 0;

  /** The number of bytes last transferred. */
  private long lastTransferredBytes = 0;

  /** The last calculated progress speed. */
  private long lastProgressSpeed;

  /** The last estimated time. */
  private long lastEstimatedTime;

  /** Creates a file conversation component. */
  public FileTransferConversationComponent() {
    imageLabel = new FileImageLabel();

    constraints.gridx = 0;
    constraints.gridy = 0;
    constraints.gridwidth = 1;
    constraints.gridheight = 4;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 5, 5);

    add(imageLabel, constraints);
    imageLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON)));

    constraints.gridx = 1;
    constraints.gridy = 0;
    constraints.gridwidth = 3;
    constraints.gridheight = 1;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.weightx = 1.0;
    constraints.anchor = GridBagConstraints.NORTHWEST;
    constraints.insets = new Insets(5, 5, 5, 5);

    add(titleLabel, constraints);
    titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 11f));

    constraints.gridx = 1;
    constraints.gridy = 1;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 5, 5);

    add(fileLabel, constraints);

    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.gridwidth = 1;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(errorIconLabel, constraints);
    errorIconLabel.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 2;
    constraints.gridwidth = 2;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.HORIZONTAL;

    add(errorArea, constraints);
    errorArea.setForeground(new Color(resources.getColor("service.gui.ERROR_FOREGROUND")));
    setTextAreaStyle(errorArea);
    errorArea.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(retryButton, constraints);
    retryButton.setText(GuiActivator.getResources().getI18NString("service.gui.RETRY"));
    retryButton.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(cancelButton, constraints);
    cancelButton.setText(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));
    cancelButton.addActionListener(this);
    cancelButton.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = GridBagConstraints.RELATIVE;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.fill = GridBagConstraints.NONE;
    constraints.anchor = GridBagConstraints.EAST;
    constraints.insets = new Insets(0, 5, 0, 5);

    constraints.gridx = 3;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.fill = GridBagConstraints.NONE;
    constraints.anchor = GridBagConstraints.LINE_END;
    constraints.insets = new Insets(0, 5, 0, 5);

    add(progressPropertiesPanel, constraints);

    estimatedTimeLabel.setFont(estimatedTimeLabel.getFont().deriveFont(11f));
    estimatedTimeLabel.setVisible(false);
    progressSpeedLabel.setFont(progressSpeedLabel.getFont().deriveFont(11f));
    progressSpeedLabel.setVisible(false);

    progressPropertiesPanel.add(progressSpeedLabel);
    progressPropertiesPanel.add(estimatedTimeLabel);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(acceptButton, constraints);
    acceptButton.setText(GuiActivator.getResources().getI18NString("service.gui.ACCEPT"));
    acceptButton.setVisible(false);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(rejectButton, constraints);
    rejectButton.setText(GuiActivator.getResources().getI18NString("service.gui.REJECT"));
    rejectButton.setVisible(false);

    constraints.gridx = 1;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(openFileButton, constraints);
    openFileButton.setText(GuiActivator.getResources().getI18NString("service.gui.OPEN"));
    openFileButton.setVisible(false);
    openFileButton.addActionListener(this);

    constraints.gridx = 2;
    constraints.gridy = 3;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 0.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.fill = GridBagConstraints.NONE;

    add(openFolderButton, constraints);
    openFolderButton.setText(GuiActivator.getResources().getI18NString("service.gui.OPEN_FOLDER"));
    openFolderButton.setVisible(false);
    openFolderButton.addActionListener(this);

    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.gridwidth = 3;
    constraints.gridheight = 1;
    constraints.weightx = 1.0;
    constraints.anchor = GridBagConstraints.WEST;
    constraints.insets = new Insets(0, 5, 0, 5);
    constraints.ipadx = 150;
    constraints.fill = GridBagConstraints.HORIZONTAL;

    add(progressBar, constraints);
    progressBar.setVisible(false);
    progressBar.setStringPainted(true);
  }

  /**
   * Sets a custom style for the given text area.
   *
   * @param textArea the text area to style
   */
  private void setTextAreaStyle(JTextArea textArea) {
    textArea.setOpaque(false);
    textArea.setLineWrap(true);
    textArea.setWrapStyleWord(true);
  }

  /**
   * Shows the given error message in the error area of this component.
   *
   * @param message the message to show
   */
  protected void showErrorMessage(String message) {
    errorArea.setText(message);
    errorIconLabel.setVisible(true);
    errorArea.setVisible(true);
  }

  /**
   * Sets the download file.
   *
   * @param file the file that has been downloaded or sent
   */
  protected void setCompletedDownloadFile(File file) {
    this.downloadFile = file;

    imageLabel.setFile(downloadFile);

    imageLabel.setToolTipText(resources.getI18NString("service.gui.OPEN_FILE_FROM_IMAGE"));

    imageLabel.addMouseListener(
        new MouseAdapter() {
          public void mouseClicked(MouseEvent e) {
            if (e.getClickCount() > 1) {
              openFile(downloadFile);
            }
          }
        });
  }

  /**
   * Sets the file transfer.
   *
   * @param fileTransfer the file transfer
   * @param transferredFileSize the size of the transferred file
   */
  protected void setFileTransfer(FileTransfer fileTransfer, long transferredFileSize) {
    this.fileTransfer = fileTransfer;
    this.transferredFileSize = transferredFileSize;

    fileTransfer.addProgressListener(this);
  }

  /**
   * Handles buttons action events.
   *
   * @param evt the <tt>ActionEvent</tt> that notified us
   */
  public void actionPerformed(ActionEvent evt) {
    JButton sourceButton = (JButton) evt.getSource();

    if (sourceButton.equals(openFileButton)) {
      this.openFile(downloadFile);
    } else if (sourceButton.equals(openFolderButton)) {
      try {
        File downloadDir = GuiActivator.getFileAccessService().getDefaultDownloadDirectory();

        GuiActivator.getDesktopService().open(downloadDir);
      } catch (IllegalArgumentException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (NullPointerException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_DOES_NOT_EXIST"));
      } catch (UnsupportedOperationException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FILE_OPEN_NOT_SUPPORTED"));
      } catch (SecurityException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_PERMISSION"));
      } catch (IOException e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open folder.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_NO_APPLICATION"));
      } catch (Exception e) {
        if (logger.isDebugEnabled()) logger.debug("Unable to open file.", e);

        this.showErrorMessage(resources.getI18NString("service.gui.FOLDER_OPEN_FAILED"));
      }
    } else if (sourceButton.equals(cancelButton)) {
      if (fileTransfer != null) fileTransfer.cancel();
    }
  }

  /**
   * Updates progress bar progress line every time a progress event has been received.
   *
   * @param event the <tt>FileTransferProgressEvent</tt> that notified us
   */
  public void progressChanged(FileTransferProgressEvent event) {
    progressBar.setValue((int) event.getProgress());

    long transferredBytes = event.getFileTransfer().getTransferedBytes();
    long progressTimestamp = event.getTimestamp();

    ByteFormat format = new ByteFormat();
    String bytesString = format.format(transferredBytes);

    if ((progressTimestamp - lastSpeedTimestamp) >= SPEED_CALCULATE_DELAY) {
      lastProgressSpeed = Math.round(calculateProgressSpeed(transferredBytes));

      this.lastSpeedTimestamp = progressTimestamp;
      this.lastTransferredBytes = transferredBytes;
    }

    if ((progressTimestamp - lastEstimatedTimeTimestamp) >= SPEED_CALCULATE_DELAY
        && lastProgressSpeed > 0) {
      lastEstimatedTime =
          Math.round(
              calculateEstimatedTransferTime(
                  lastProgressSpeed, transferredFileSize - transferredBytes));

      lastEstimatedTimeTimestamp = progressTimestamp;
    }

    progressBar.setString(getProgressLabel(bytesString));

    if (lastProgressSpeed > 0) {
      progressSpeedLabel.setText(
          resources.getI18NString("service.gui.SPEED") + format.format(lastProgressSpeed) + "/sec");
      progressSpeedLabel.setVisible(true);
    }

    if (lastEstimatedTime > 0) {
      estimatedTimeLabel.setText(
          resources.getI18NString("service.gui.ESTIMATED_TIME")
              + GuiUtils.formatSeconds(lastEstimatedTime * 1000));
      estimatedTimeLabel.setVisible(true);
    }
  }

  /**
   * Returns the string, showing information for the given file.
   *
   * @param file the file
   * @return the name of the given file
   */
  protected String getFileLabel(File file) {
    String fileName = file.getName();
    long fileSize = file.length();

    ByteFormat format = new ByteFormat();
    String text = format.format(fileSize);

    return fileName + " (" + text + ")";
  }

  /**
   * Returns the string, showing information for the given file.
   *
   * @param fileName the name of the file
   * @param fileSize the size of the file
   * @return the name of the given file
   */
  protected String getFileLabel(String fileName, long fileSize) {
    ByteFormat format = new ByteFormat();
    String text = format.format(fileSize);

    return fileName + " (" + text + ")";
  }

  /** Hides all progress related components. */
  protected void hideProgressRelatedComponents() {
    progressBar.setVisible(false);
    progressSpeedLabel.setVisible(false);
    estimatedTimeLabel.setVisible(false);
  }

  /**
   * Returns the label to show on the progress bar.
   *
   * @param bytesString the bytes that have been transfered
   * @return the label to show on the progress bar
   */
  protected abstract String getProgressLabel(String bytesString);

  /**
   * Returns the speed of the transfer.
   *
   * @param transferredBytes the number of bytes that have been transferred
   * @return the speed of the transfer
   */
  private double calculateProgressSpeed(long transferredBytes) {
    // Bytes per second = bytes / SPEED_CALCULATE_DELAY miliseconds * 1000.
    return (transferredBytes - lastTransferredBytes) / SPEED_CALCULATE_DELAY * 1000;
  }

  /**
   * Returns the estimated transfer time left.
   *
   * @param speed the speed of the transfer
   * @param bytesLeft the size of the file
   * @return the estimated transfer time left
   */
  private double calculateEstimatedTransferTime(double speed, long bytesLeft) {
    return bytesLeft / speed;
  }

  /** Reload images and colors. */
  public void loadSkin() {
    errorIconLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.EXCLAMATION_MARK)));

    if (downloadFile != null)
      imageLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_FILE_ICON)));

    errorArea.setForeground(new Color(resources.getColor("service.gui.ERROR_FOREGROUND")));
  }
}
/**
 * The <tt>OneToOneCallPeerPanel</tt> is the panel containing data for a call peer in a given call.
 * It contains information like call peer name, photo, call duration, etc.
 *
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Sebastien Vincent
 * @author Adam Netocny
 */
public class OneToOneCallPeerPanel extends TransparentPanel
    implements SwingCallPeerRenderer, PropertyChangeListener, Skinnable {
  /**
   * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and its instances for
   * logging output.
   */
  private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class);

  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>CallPeer</tt>, which is rendered in this panel. */
  private final CallPeer callPeer;

  /** The <tt>Call</tt>, which is rendered in this panel. */
  private final Call call;

  /**
   * The <tt>CallPeerAdapter</tt> which implements common <tt>CallPeer</tt>-related listeners on
   * behalf of this instance.
   */
  private final CallPeerAdapter callPeerAdapter;

  /** The renderer of the call. */
  private final SwingCallRenderer callRenderer;

  /** The component showing the status of the underlying call peer. */
  private final JLabel callStatusLabel = new JLabel();

  /** The center component. */
  private final VideoContainer center;

  /**
   * The AWT <tt>Component</tt> which implements a button which allows closing/hiding the visual
   * <tt>Component</tt> which depicts the video streaming from the local peer/user to the remote
   * peer(s).
   */
  private Component closeLocalVisualComponentButton;

  /**
   * A listener to desktop sharing granted/revoked events and to mouse and keyboard interaction with
   * the remote video displaying the remote desktop.
   */
  private final DesktopSharingMouseAndKeyboardListener desktopSharingMouseAndKeyboardListener;

  /**
   * The indicator which determines whether {@link #dispose()} has already been invoked on this
   * instance. If <tt>true</tt>, this instance is considered non-functional and is to be left to the
   * garbage collector.
   */
  private boolean disposed = false;

  /** The DTMF label. */
  private final JLabel dtmfLabel = new JLabel();

  /** The component responsible for displaying an error message. */
  private JTextComponent errorMessageComponent;

  /** The label showing whether the call is on or off hold. */
  private final JLabel holdStatusLabel = new JLabel();

  /** Sound local level label. */
  private InputVolumeControlButton localLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component localVideo;

  /** The label showing whether the voice has been set to mute. */
  private final JLabel muteStatusLabel = new JLabel();

  /** The <tt>Icon</tt> which represents the avatar of the associated call peer. */
  private ImageIcon peerImage;

  /** The name of the peer. */
  private String peerName;

  /** The label containing the user photo. */
  private final JLabel photoLabel;

  /** Sound remote level label. */
  private Component remoteLevel;

  /**
   * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to
   * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the remote
   * peer(s) to the local peer/user.
   *
   * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the
   * current state of the model of this view.
   */
  private Component remoteVideo;

  /** Current id for security image. */
  private ImageID securityImageID = ImageLoader.SECURE_BUTTON_OFF;

  /** The panel containing security related components. */
  private SecurityPanel<?> securityPanel;

  /** The security status of the peer */
  private final SecurityStatusLabel securityStatusLabel = new SecurityStatusLabel();

  /** The status bar component. */
  private final Component statusBar;

  /** The facility which aids this instance in the dealing with the video-related information. */
  private final UIVideoHandler2 uiVideoHandler;

  /**
   * The <tt>Observer</tt> which listens to changes in the video-related information detected and
   * reported by {@link #uiVideoHandler}.
   */
  private final Observer uiVideoHandlerObserver =
      new Observer() {
        public void update(Observable o, Object arg) {
          updateViewFromModel();
        }
      };

  /**
   * The <tt>Runnable</tt> which is scheduled by {@link #updateViewFromModel()} for execution in the
   * AWT event dispatching thread in order to invoke {@link
   * #updateViewFromModelInEventDispatchThread()}.
   */
  private final Runnable updateViewFromModelInEventDispatchThread =
      new Runnable() {
        public void run() {
          /*
           * We receive events/notifications from various threads and we
           * respond to them in the AWT event dispatching thread. It is
           * possible to first schedule an event to be brought to the AWT
           * event dispatching thread, then to have #dispose() invoked on
           * this instance and, finally, to receive the scheduled event in
           * the AWT event dispatching thread. In such a case, this
           * disposed instance should not respond to the event.
           */
          if (!disposed) updateViewFromModelInEventDispatchThread();
        }
      };

  /**
   * Creates a <tt>CallPeerPanel</tt> for the given call peer.
   *
   * @param callRenderer the renderer of the call
   * @param callPeer the <tt>CallPeer</tt> represented in this panel
   * @param uiVideoHandler the facility which is to aid the new instance in the dealing with the
   *     video-related information
   */
  public OneToOneCallPeerPanel(
      SwingCallRenderer callRenderer, CallPeer callPeer, UIVideoHandler2 uiVideoHandler) {
    this.callRenderer = callRenderer;
    this.callPeer = callPeer;
    // we need to obtain call as soon as possible
    // cause if it fails too quickly we may fail to show it
    this.call = callPeer.getCall();
    this.uiVideoHandler = uiVideoHandler;

    peerName = CallManager.getPeerDisplayName(callPeer);
    securityPanel = SecurityPanel.create(this, callPeer, null);

    photoLabel = new JLabel(getPhotoLabelIcon());
    center = createCenter();
    statusBar = createStatusBar();

    setPeerImage(CallManager.getPeerImage(callPeer));

    /* Lay out the main Components of the UI. */
    setLayout(new GridBagLayout());

    GridBagConstraints cnstrnts = new GridBagConstraints();

    if (center != null) {
      cnstrnts.fill = GridBagConstraints.BOTH;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 1;
      cnstrnts.weightx = 1;
      cnstrnts.weighty = 1;
      add(center, cnstrnts);
    }
    if (statusBar != null) {
      cnstrnts.fill = GridBagConstraints.NONE;
      cnstrnts.gridx = 0;
      cnstrnts.gridy = 3;
      cnstrnts.weightx = 0;
      cnstrnts.weighty = 0;
      cnstrnts.insets = new Insets(5, 0, 0, 0);
      add(statusBar, cnstrnts);
    }

    createSoundLevelIndicators();
    initSecuritySettings();

    /*
     * Add the listeners which will be notified about changes in the model
     * and which will update this view.
     */
    callPeerAdapter = new CallPeerAdapter(callPeer, this);
    uiVideoHandler.addObserver(uiVideoHandlerObserver);

    /*
     * This view adapts to whether it is displayed in full-screen or
     * windowed mode.
     */
    if (callRenderer instanceof Component) {
      ((Component) callRenderer).addPropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }

    OperationSetDesktopSharingClient desktopSharingClient =
        callPeer.getProtocolProvider().getOperationSet(OperationSetDesktopSharingClient.class);
    if (desktopSharingClient != null) {
      desktopSharingMouseAndKeyboardListener =
          new DesktopSharingMouseAndKeyboardListener(callPeer, desktopSharingClient);
    } else desktopSharingMouseAndKeyboardListener = null;

    updateViewFromModel();
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the central area of this <tt>CallPeerPanel</tt>
   * which displays the photo of the <tt>CallPeer</tt> or the video if any.
   */
  private VideoContainer createCenter() {
    photoLabel.setPreferredSize(new Dimension(90, 90));

    return createVideoContainer(photoLabel);
  }

  /** Creates sound level related components. */
  private void createSoundLevelIndicators() {
    TransparentPanel localLevelPanel = new TransparentPanel(new BorderLayout(5, 0));
    TransparentPanel remoteLevelPanel = new TransparentPanel(new BorderLayout(5, 0));

    localLevel =
        new InputVolumeControlButton(
            call, ImageLoader.MICROPHONE, ImageLoader.MUTE_BUTTON, false, false);
    remoteLevel =
        new OutputVolumeControlButton(call.getConference(), ImageLoader.HEADPHONE, false, false)
            .getComponent();

    final SoundLevelIndicator localLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);
    final SoundLevelIndicator remoteLevelIndicator =
        new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL);

    localLevelPanel.add(localLevel, BorderLayout.WEST);
    localLevelPanel.add(localLevelIndicator, BorderLayout.CENTER);
    remoteLevelPanel.add(remoteLevel, BorderLayout.WEST);
    remoteLevelPanel.add(remoteLevelIndicator, BorderLayout.CENTER);

    GridBagConstraints constraints = new GridBagConstraints();
    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 5;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(10, 0, 0, 0);

    add(localLevelPanel, constraints);

    constraints.fill = GridBagConstraints.NONE;
    constraints.gridx = 0;
    constraints.gridy = 6;
    constraints.weightx = 0;
    constraints.weighty = 0;
    constraints.insets = new Insets(5, 0, 10, 0);

    add(remoteLevelPanel, constraints);

    if (!GuiActivator.getConfigurationService()
        .getBoolean(
            "net.java.sip.communicator.impl.gui.main.call." + "DISABLE_SOUND_LEVEL_INDICATORS",
            false)) {
      callPeer.addStreamSoundLevelListener(
          new SoundLevelListener() {
            public void soundLevelChanged(Object source, int level) {
              remoteLevelIndicator.updateSoundLevel(level);
            }
          });
      /*
       * By the time the UI gets to be initialized, the callPeer may have
       * been removed from its Call. As far as the UI is concerned, the
       * callPeer will never have a Call again and there will be no audio
       * levels to display anyway so there is no point in throwing a
       * NullPointerException here.
       */
      if (call != null) {
        call.addLocalUserSoundLevelListener(
            new SoundLevelListener() {
              public void soundLevelChanged(Object source, int level) {
                localLevelIndicator.updateSoundLevel(level);
              }
            });
      }
    }
  }

  /**
   * Creates the <tt>Component</tt> hierarchy of the area of status-related information such as
   * <tt>CallPeer</tt> display name, call duration, security status.
   *
   * @return the root of the <tt>Component</tt> hierarchy of the area of status-related information
   *     such as <tt>CallPeer</tt> display name, call duration, security status
   */
  private Component createStatusBar() {
    // stateLabel
    callStatusLabel.setForeground(Color.WHITE);
    dtmfLabel.setForeground(Color.WHITE);
    callStatusLabel.setText(callPeer.getState().getLocalizedStateString());

    PeerStatusPanel statusPanel = new PeerStatusPanel(new GridBagLayout());

    GridBagConstraints constraints = new GridBagConstraints();

    constraints.gridx = 0;
    constraints.gridy = 0;
    statusPanel.add(securityStatusLabel, constraints);
    initSecurityStatusLabel();

    constraints.gridx++;
    statusPanel.add(holdStatusLabel, constraints);

    constraints.gridx++;
    statusPanel.add(muteStatusLabel, constraints);

    constraints.gridx++;
    callStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 12));
    statusPanel.add(callStatusLabel, constraints);

    constraints.gridx++;
    constraints.weightx = 1f;
    statusPanel.add(dtmfLabel, constraints);

    return statusPanel;
  }

  /**
   * Creates a new AWT <tt>Container</tt> which can display a single <tt>Component</tt> at a time
   * (supposedly, one which represents video) and, in the absence of such a <tt>Component</tt>,
   * displays a predefined default <tt>Component</tt> (in accord with the previous supposition, one
   * which is the default when there is no video). The returned <tt>Container</tt> will track the
   * <tt>Components</tt>s added to and removed from it in order to make sure that
   * <tt>noVideoContainer</tt> is displayed as described.
   *
   * @param noVideoComponent the predefined default <tt>Component</tt> to be displayed in the
   *     returned <tt>Container</tt> when there is no other <tt>Component</tt> in it
   * @return a new <tt>Container</tt> which can display a single <tt>Component</tt> at a time and,
   *     in the absence of such a <tt>Component</tt>, displays <tt>noVideoComponent</tt>
   */
  private VideoContainer createVideoContainer(Component noVideoComponent) {
    Container oldParent = noVideoComponent.getParent();

    if (oldParent != null) oldParent.remove(noVideoComponent);

    return new VideoContainer(noVideoComponent, false);
  }

  /**
   * Releases the resources acquired by this instance which require explicit disposal (e.g. any
   * listeners added to the depicted <tt>CallPeer</tt>. Invoked by <tt>OneToOneCallPanel</tt> when
   * it determines that this <tt>OneToOneCallPeerPanel</tt> is no longer necessary.
   */
  public void dispose() {
    disposed = true;

    callPeerAdapter.dispose();
    uiVideoHandler.deleteObserver(uiVideoHandlerObserver);

    if (callRenderer instanceof Component) {
      ((Component) callRenderer).removePropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this);
    }
  }

  /**
   * Returns the parent <tt>CallPanel</tt> containing this renderer.
   *
   * @return the parent <tt>CallPanel</tt> containing this renderer
   */
  public CallPanel getCallPanel() {
    return callRenderer.getCallContainer();
  }

  /**
   * Returns the parent call renderer.
   *
   * @return the parent call renderer
   */
  public CallRenderer getCallRenderer() {
    return callRenderer;
  }

  /**
   * Returns the component associated with this renderer.
   *
   * @return the component associated with this renderer
   */
  public Component getComponent() {
    return this;
  }

  /**
   * Returns the name of the peer, contained in this panel.
   *
   * @return the name of the peer, contained in this panel
   */
  public String getPeerName() {
    return peerName;
  }

  /**
   * Gets the <tt>Icon</tt> to be displayed in {@link #photoLabel}.
   *
   * @return the <tt>Icon</tt> to be displayed in {@link #photoLabel}
   */
  private ImageIcon getPhotoLabelIcon() {
    return (peerImage == null)
        ? new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))
        : peerImage;
  }

  /** Initializes the security settings for this call peer. */
  private void initSecuritySettings() {
    CallPeerSecurityStatusEvent securityEvent = callPeer.getCurrentSecuritySettings();

    if (securityEvent instanceof CallPeerSecurityOnEvent)
      securityOn((CallPeerSecurityOnEvent) securityEvent);
  }

  /** Initializes the security status label, shown in the call status bar. */
  private void initSecurityStatusLabel() {
    securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));

    securityStatusLabel.addMouseListener(
        new MouseAdapter() {
          /** Invoked when a mouse button has been pressed on a component. */
          @Override
          public void mousePressed(MouseEvent e) {
            // Only show the security details if the security is on.
            SrtpControl ctrl = securityPanel.getSecurityControl();
            if (ctrl instanceof ZrtpControl && ctrl.getSecureCommunicationStatus()) {
              setSecurityPanelVisible(
                  !callRenderer
                      .getCallContainer()
                      .getCallWindow()
                      .getFrame()
                      .getGlassPane()
                      .isVisible());
            }
          }
        });
  }

  /**
   * Determines whether the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s) is currently visible.
   *
   * @return <tt>true</tt> if the visual <tt>Component</tt> depicting the video streaming from the
   *     local peer/user to the remote peer(s) is currently visible; otherwise, <tt>false</tt>
   */
  public boolean isLocalVideoVisible() {
    return uiVideoHandler.isLocalVideoVisible();
  }

  /** Reloads all icons. */
  public void loadSkin() {
    if (localLevel != null)
      localLevel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MICROPHONE)));

    if (remoteLevel != null && remoteLevel instanceof Skinnable)
      ((Skinnable) remoteLevel).loadSkin();

    if (muteStatusLabel.getIcon() != null)
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));

    if (holdStatusLabel.getIcon() != null)
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));

    securityStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(securityImageID)));

    if (peerImage == null) {
      photoLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)));
    }
  }

  /**
   * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>.
   *
   * @param dtmfChar the DTMF char to print
   */
  public void printDTMFTone(char dtmfChar) {
    dtmfLabel.setText(dtmfLabel.getText() + dtmfChar);
    if (dtmfLabel.getBorder() == null)
      dtmfLabel.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 5));
  }

  /**
   * Notifies this instance about a change in the value of a property of a source which of interest
   * to this instance. For example, <tt>OneToOneCallPeerPanel</tt> updates its user
   * interface-related properties upon changes in the value of the {@link
   * CallContainer#PROP_FULL_SCREEN} property of its associated {@link #callRenderer}.
   *
   * @param ev a <tt>PropertyChangeEvent</tt> which identifies the source, the name of the property
   *     and the old and new values
   */
  public void propertyChange(PropertyChangeEvent ev) {
    if (CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) updateViewFromModel();
  }

  /**
   * Re-dispatches glass pane mouse events only in case they occur on the security panel.
   *
   * @param glassPane the glass pane
   * @param e the mouse event in question
   */
  private void redispatchMouseEvent(Component glassPane, MouseEvent e) {
    Point glassPanePoint = e.getPoint();

    Point securityPanelPoint =
        SwingUtilities.convertPoint(glassPane, glassPanePoint, securityPanel);

    Component component;
    Point componentPoint;

    if (securityPanelPoint.y > 0) {
      component = securityPanel;
      componentPoint = securityPanelPoint;
    } else {
      Container contentPane =
          callRenderer.getCallContainer().getCallWindow().getFrame().getContentPane();

      Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane);

      component =
          SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y);

      componentPoint = SwingUtilities.convertPoint(contentPane, glassPanePoint, component);
    }

    if (component != null)
      component.dispatchEvent(
          new MouseEvent(
              component,
              e.getID(),
              e.getWhen(),
              e.getModifiers(),
              componentPoint.x,
              componentPoint.y,
              e.getClickCount(),
              e.isPopupTrigger()));

    e.consume();
  }

  /**
   * The handler for the security event received. The security event for starting establish a secure
   * connection.
   *
   * @param evt the security started event received
   */
  public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent evt) {
    if (Boolean.parseBoolean(
        GuiActivator.getResources().getSettingsString("impl.gui.PARANOIA_UI"))) {
      SrtpControl srtpControl = null;
      if (callPeer instanceof MediaAwareCallPeer) srtpControl = evt.getSecurityController();

      securityPanel = new ParanoiaTimerSecurityPanel<SrtpControl>(srtpControl);

      setSecurityPanelVisible(true);
    }
  }

  /**
   * Indicates that the security has gone off.
   *
   * @param evt the <tt>CallPeerSecurityOffEvent</tt> that notified us
   */
  public void securityOff(final CallPeerSecurityOffEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOff(evt);
            }
          });
      return;
    }

    if (evt.getSessionType() == CallPeerSecurityOffEvent.AUDIO_SESSION) {
      securityStatusLabel.setText("");
      securityStatusLabel.setSecurityOff();
      if (securityStatusLabel.getBorder() == null)
        securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 3));
    }

    securityPanel.securityOff(evt);
  }

  /**
   * Indicates that the security is turned on.
   *
   * <p>Sets the secured status icon to the status panel and initializes/updates the corresponding
   * security details.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityOn(final CallPeerSecurityOnEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityOn(evt);
            }
          });
      return;
    }

    // If the securityOn is called without a specific event, we'll just set
    // the security label status to on.
    if (evt == null) {
      securityStatusLabel.setSecurityOn();
      return;
    }

    SrtpControl srtpControl = evt.getSecurityController();

    if (!srtpControl.requiresSecureSignalingTransport()
        || callPeer.getProtocolProvider().isSignalingTransportSecure()) {
      if (srtpControl instanceof ZrtpControl) {
        securityStatusLabel.setText("zrtp");

        if (!((ZrtpControl) srtpControl).isSecurityVerified())
          securityStatusLabel.setSecurityPending();
        else securityStatusLabel.setSecurityOn();
      } else securityStatusLabel.setSecurityOn();
    }

    // if we have some other panel, using other control
    if (!srtpControl.getClass().isInstance(securityPanel.getSecurityControl())
        || (securityPanel instanceof ParanoiaTimerSecurityPanel)) {
      setSecurityPanelVisible(false);

      securityPanel = SecurityPanel.create(this, callPeer, srtpControl);

      if (srtpControl instanceof ZrtpControl)
        ((ZrtpSecurityPanel) securityPanel).setSecurityStatusLabel(securityStatusLabel);
    }

    securityPanel.securityOn(evt);

    boolean isSecurityLowPriority =
        Boolean.parseBoolean(
            GuiActivator.getResources()
                .getSettingsString("impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY"));

    // Display ZRTP panel in case SAS was not verified or a AOR mismtach
    // was detected during creation of ZrtpSecurityPanel.
    // Don't show panel if user does not care about security at all.
    if (srtpControl instanceof ZrtpControl
        && !isSecurityLowPriority
        && (!((ZrtpControl) srtpControl).isSecurityVerified()
            || ((ZrtpSecurityPanel) securityPanel).isZidAorMismatch())) {
      setSecurityPanelVisible(true);
    }

    this.revalidate();
  }

  /** Indicates that the security status is pending confirmation. */
  public void securityPending() {
    securityStatusLabel.setSecurityPending();
  }

  /**
   * Indicates that the security is timeouted, is not supported by the other end.
   *
   * @param evt Details about the event that caused this message.
   */
  public void securityTimeout(final CallPeerSecurityTimeoutEvent evt) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              securityTimeout(evt);
            }
          });
      return;
    }

    if (securityPanel != null) securityPanel.securityTimeout(evt);
  }

  /**
   * Sets the reason of a call failure if one occurs. The renderer should display this reason to the
   * user.
   *
   * @param reason the reason to display
   */
  public void setErrorReason(final String reason) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setErrorReason(reason);
            }
          });
      return;
    }

    if (errorMessageComponent == null) {
      errorMessageComponent = new JTextPane();

      JTextPane textPane = (JTextPane) errorMessageComponent;
      textPane.setEditable(false);
      textPane.setOpaque(false);

      StyledDocument doc = textPane.getStyledDocument();

      MutableAttributeSet standard = new SimpleAttributeSet();
      StyleConstants.setAlignment(standard, StyleConstants.ALIGN_CENTER);
      StyleConstants.setFontFamily(standard, textPane.getFont().getFamily());
      StyleConstants.setFontSize(standard, 12);
      doc.setParagraphAttributes(0, 0, standard, true);

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

      add(errorMessageComponent, constraints);
      this.revalidate();
    }

    errorMessageComponent.setText(reason);

    if (isVisible()) errorMessageComponent.repaint();
  }

  /**
   * Shows/hides the visual <tt>Component</tt> depicting the video streaming from the local
   * peer/user to the remote peer(s).
   *
   * @param visible <tt>true</tt> to show the visual <tt>Component</tt> depicting the video
   *     streaming from the local peer/user to the remote peer(s); <tt>false</tt>, otherwise
   */
  public void setLocalVideoVisible(boolean visible) {
    uiVideoHandler.setLocalVideoVisible(visible);
  }

  /**
   * Sets the mute status icon to the status panel.
   *
   * @param isMute indicates if the call with this peer is muted
   */
  public void setMute(final boolean isMute) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setMute(isMute);
            }
          });
      return;
    }

    if (isMute) {
      muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON)));
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      muteStatusLabel.setIcon(null);
      muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    // Update input volume control button state to reflect the current
    // mute status.
    if (localLevel.isSelected() != isMute) localLevel.setSelected(isMute);

    this.revalidate();
    this.repaint();
  }

  /**
   * Sets the "on hold" property value.
   *
   * @param isOnHold indicates if the call with this peer is put on hold
   */
  public void setOnHold(final boolean isOnHold) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setOnHold(isOnHold);
            }
          });
      return;
    }

    if (isOnHold) {
      holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON)));
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3));
    } else {
      holdStatusLabel.setIcon(null);
      holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
    }

    this.revalidate();
    this.repaint();
  }

  /**
   * Set the image of the peer
   *
   * @param image new image
   */
  public void setPeerImage(byte[] image) {
    // If the image is still null we try to obtain it from one of the
    // available contact sources.
    if (image == null || image.length <= 0) {
      GuiActivator.getContactList().setSourceContactImage(peerName, photoLabel, 100, 100);
    } else {
      peerImage = ImageUtils.getScaledRoundedIcon(image, 100, 100);
      if (peerImage == null) peerImage = getPhotoLabelIcon();

      if (!SwingUtilities.isEventDispatchThread()) {
        SwingUtilities.invokeLater(
            new Runnable() {
              public void run() {
                photoLabel.setIcon(peerImage);
                photoLabel.repaint();
              }
            });
      } else {
        photoLabel.setIcon(peerImage);
        photoLabel.repaint();
      }
    }
  }

  /**
   * Sets the name of the peer.
   *
   * @param name the name of the peer
   */
  public void setPeerName(final String name) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerName(name);
            }
          });
      return;
    }

    peerName = name;

    ((OneToOneCallPanel) callRenderer).setPeerName(name);
  }

  /**
   * Sets the state of the contained call peer by specifying the state name.
   *
   * @param oldState the previous state of the peer
   * @param newState the new state of the peer
   * @param stateString the state of the contained call peer
   */
  public void setPeerState(
      final CallPeerState oldState, final CallPeerState newState, final String stateString) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setPeerState(oldState, newState, stateString);
            }
          });
      return;
    }

    this.callStatusLabel.setText(stateString);

    if (newState == CallPeerState.CONNECTED
        && !CallPeerState.isOnHold(oldState)
        && !securityStatusLabel.isSecurityStatusSet()) {
      securityStatusLabel.setSecurityOff();
    }
  }

  /**
   * Shows/hides the security panel.
   *
   * @param isVisible <tt>true</tt> to show the security panel, <tt>false</tt> to hide it
   */
  public void setSecurityPanelVisible(final boolean isVisible) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              setSecurityPanelVisible(isVisible);
            }
          });
      return;
    }

    final JFrame callFrame = callRenderer.getCallContainer().getCallWindow().getFrame();

    final JPanel glassPane = (JPanel) callFrame.getGlassPane();

    if (!isVisible) {
      // Need to hide the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(false);
      glassPane.setVisible(false);
      glassPane.removeAll();
    } else {
      glassPane.setLayout(null);
      glassPane.addMouseListener(
          new MouseListener() {
            public void mouseClicked(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseEntered(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseExited(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mousePressed(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }

            public void mouseReleased(MouseEvent e) {
              redispatchMouseEvent(glassPane, e);
            }
          });

      Point securityLabelPoint = securityStatusLabel.getLocation();

      Point newPoint =
          SwingUtilities.convertPoint(
              securityStatusLabel.getParent(),
              securityLabelPoint.x,
              securityLabelPoint.y,
              callFrame);

      securityPanel.setBeginPoint(new Point((int) newPoint.getX() + 15, 0));
      securityPanel.setBounds(0, (int) newPoint.getY() - 5, this.getWidth(), 130);

      glassPane.add(securityPanel);
      // Need to show the security panel explicitly in order to keep the
      // fade effect.
      securityPanel.setVisible(true);
      glassPane.setVisible(true);

      glassPane.addComponentListener(
          new ComponentAdapter() {
            /** Invoked when the component's size changes. */
            @Override
            public void componentResized(ComponentEvent e) {
              if (glassPane.isVisible()) {
                glassPane.setVisible(false);
                callFrame.removeComponentListener(this);
              }
            }
          });
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>.
   */
  private void updateViewFromModel() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (!disposed) {
      if (SwingUtilities.isEventDispatchThread()) updateViewFromModelInEventDispatchThread();
      else {
        SwingUtilities.invokeLater(updateViewFromModelInEventDispatchThread);
      }
    }
  }

  /**
   * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of
   * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread.
   */
  private void updateViewFromModelInEventDispatchThread() {
    /*
     * We receive events/notifications from various threads and we respond
     * to them in the AWT event dispatching thread. It is possible to first
     * schedule an event to be brought to the AWT event dispatching thread,
     * then to have #dispose() invoked on this instance and, finally, to
     * receive the scheduled event in the AWT event dispatching thread. In
     * such a case, this disposed instance should not respond to the event
     * because it may, for example, steal a visual Components depicting
     * video (which cannot belong to more than one parent at a time) from
     * another non-disposed OneToOneCallPeerPanel.
     */
    if (disposed) return;

    /*
     * Update the display of visual <tt>Component</tt>s depicting video
     * streaming between the local peer/user and the remote peer(s).
     */

    OperationSetVideoTelephony videoTelephony =
        callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class);
    Component remoteVideo = null;
    Component localVideo = null;

    if (videoTelephony != null) {
      List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer);

      if ((remoteVideos != null) && !remoteVideos.isEmpty()) {
        /*
         * TODO OneToOneCallPeerPanel displays a one-to-one conversation
         * between the local peer/user and a specific remote peer. If
         * the remote peer is the focus of a telephony conference of its
         * own, it may be sending multiple videos to the local peer.
         * Switching to a user interface which displays multiple videos
         * is the responsibility of whoever decided that this
         * OneToOneCallPeerPanel is to be used to depict the current
         * state of the CallConference associated with the CallPeer
         * depicted by this instance. If that switching decides that
         * this instance is to continue being the user interface, then
         * we should probably pick up the remote video which is
         * generated by the remote peer and not one of its
         * ConferenceMembers.
         */
        remoteVideo = remoteVideos.get(0);
      }

      if (uiVideoHandler.isLocalVideoVisible()) {
        try {
          localVideo = videoTelephony.getLocalVisualComponent(callPeer);
        } catch (OperationFailedException ofe) {
          /*
           * Well, we cannot do much about the exception. We'll just
           * not display the local video.
           */
          logger.warn("Failed to retrieve local video to be displayed.", ofe);
        }
      }

      /*
       * Determine whether there is actually a change in the local and
       * remote videos which requires an update.
       */
      boolean localVideoChanged =
          ((localVideo != this.localVideo)
              || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo)));
      boolean remoteVideoChanged =
          ((remoteVideo != this.remoteVideo)
              || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo)));

      // If the remote video has changed, maybe the CallPanel can display
      // the LO/SD/HD button.
      if (remoteVideoChanged) {
        // Updates video component which may listen the mouse and key
        // events.
        if (desktopSharingMouseAndKeyboardListener != null) {
          desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo);
        }

        CallPanel callPanel = callRenderer.getCallContainer();
        // The remote video has been added, then tries to display the
        // LO/SD/HD button.
        if (remoteVideo != null) {
          callPanel.addRemoteVideoSpecificComponents(callPeer);
        }
        // The remote video has been removed, then hide the LO/SD/HD
        // button if it is currently displayed.
        else {
          callPanel.removeRemoteVideoSpecificComponents();
        }
      }

      if (localVideoChanged || remoteVideoChanged) {
        /*
         * VideoContainer and JAWTRenderer cannot handle random
         * additions of Components. Removing the localVideo when the
         * user has requests its hiding though, should work without
         * removing all Components from the VideoCotainer and adding
         * them again.
         */
        if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) {
          if (this.localVideo != null) {
            center.remove(this.localVideo);
            this.localVideo = null;

            if (closeLocalVisualComponentButton != null)
              center.remove(closeLocalVisualComponentButton);
          }
        } else {
          center.removeAll();
          this.localVideo = null;
          this.remoteVideo = null;

          /*
           * AWT does not make a guarantee about the Z order even
           * within an operating system i.e. the order of adding the
           * Components to their Container does not mean that they
           * will be determinedly painted in that or reverse order.
           * Anyway, there appears to be an expectation among the
           * developers less acquainted with AWT that AWT paints the
           * Components of a Container in an order that is the reverse
           * of the order of their adding. In order to satisfy that
           * expectation and thus give at least some idea to the
           * developers reading the code bellow, do add the Components
           * according to that expectation.
           */

          if (localVideo != null) {
            if (closeLocalVisualComponentButton == null) {
              closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler);
            }
            center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1);

            center.add(localVideo, VideoLayout.LOCAL, -1);
            this.localVideo = localVideo;
          }

          if (remoteVideo != null) {
            center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1);
            this.remoteVideo = remoteVideo;
          }
        }
      }
    }
  }

  /** The <tt>TransparentPanel</tt> that will display the peer status. */
  private static class PeerStatusPanel extends TransparentPanel {
    /**
     * Silence the serial warning. Though there isn't a plan to serialize the instances of the
     * class, there're no fields so the default serialization routine will work.
     */
    private static final long serialVersionUID = 0L;

    /**
     * Constructs a new <tt>PeerStatusPanel</tt>.
     *
     * @param layout the <tt>LayoutManager</tt> to use
     */
    public PeerStatusPanel(LayoutManager layout) {
      super(layout);
    }

    /** @{inheritDoc} */
    @Override
    public void paintComponent(Graphics g) {
      super.paintComponent(g);

      g = g.create();

      try {
        AntialiasingManager.activateAntialiasing(g);

        g.setColor(Color.DARK_GRAY);
        g.fillRoundRect(0, 0, this.getWidth(), this.getHeight(), 10, 10);
      } finally {
        g.dispose();
      }
    }
  }
}
/**
 * The <tt>NewStatusMessageDialog</tt> is the dialog containing the form for changing the status
 * message for a protocol provider.
 *
 * @author Yana Stamcheva
 * @author Adam Netocny
 */
public class NewStatusMessageDialog extends SIPCommDialog implements ActionListener, Skinnable {
  /** The Object used for logging. */
  private final Logger logger = Logger.getLogger(NewStatusMessageDialog.class);

  /** The field, containing the status message. */
  private final JTextField messageTextField = new JTextField();

  /** The button, used to cancel this dialog. */
  private final JButton cancelButton =
      new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

  /** The presence operation set through which we change the status message. */
  private final OperationSetPresence presenceOpSet;

  /** Message panel. */
  private JPanel messagePanel;

  /**
   * Creates an instance of <tt>NewStatusMessageDialog</tt>.
   *
   * @param protocolProvider the <tt>ProtocolProviderService</tt>.
   */
  public NewStatusMessageDialog(ProtocolProviderService protocolProvider) {
    presenceOpSet =
        (OperationSetPersistentPresence)
            protocolProvider.getOperationSet(OperationSetPresence.class);

    this.init();
    pack();
  }

  /** Initializes the <tt>NewStatusMessageDialog</tt> by adding the buttons, fields, etc. */
  private void init() {
    JLabel messageLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel dataPanel = new TransparentPanel(new BorderLayout(5, 5));

    JTextArea infoArea =
        new JTextArea(GuiActivator.getResources().getI18NString("service.gui.STATUS_MESSAGE_INFO"));

    JLabel infoTitleLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    JPanel labelsPanel = new TransparentPanel(new GridLayout(0, 1));

    JButton okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK"));

    JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.RIGHT));

    this.setTitle(GuiActivator.getResources().getI18NString("service.gui.NEW_STATUS_MESSAGE"));

    this.getRootPane().setDefaultButton(okButton);

    this.setPreferredSize(new Dimension(500, 200));

    infoArea.setEditable(false);
    infoArea.setLineWrap(true);
    infoArea.setWrapStyleWord(true);
    infoArea.setOpaque(false);

    dataPanel.add(messageLabel, BorderLayout.WEST);

    messageTextField.setText(presenceOpSet.getCurrentStatusMessage());
    dataPanel.add(messageTextField, BorderLayout.CENTER);

    infoTitleLabel.setHorizontalAlignment(JLabel.CENTER);
    infoTitleLabel.setFont(infoTitleLabel.getFont().deriveFont(Font.BOLD, 18.0f));

    labelsPanel.add(infoTitleLabel);
    labelsPanel.add(infoArea);
    labelsPanel.add(dataPanel);

    messagePanel = new TransparentPanel(new GridBagLayout());
    GridBagConstraints messagePanelConstraints = new GridBagConstraints();
    messagePanelConstraints.anchor = GridBagConstraints.NORTHWEST;
    messagePanelConstraints.fill = GridBagConstraints.NONE;
    messagePanelConstraints.gridx = 0;
    messagePanelConstraints.gridy = 0;
    messagePanelConstraints.insets = new Insets(5, 0, 5, 10);
    messagePanelConstraints.weightx = 0;
    messagePanelConstraints.weighty = 0;
    messagePanel.add(
        new ImageCanvas(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON)),
        messagePanelConstraints);

    messagePanelConstraints.anchor = GridBagConstraints.NORTH;
    messagePanelConstraints.fill = GridBagConstraints.HORIZONTAL;
    messagePanelConstraints.gridx = 1;
    messagePanelConstraints.insets = new Insets(0, 0, 0, 0);
    messagePanelConstraints.weightx = 1;
    messagePanel.add(labelsPanel, messagePanelConstraints);

    okButton.setName("ok");
    cancelButton.setName("cancel");

    okButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.OK"));
    cancelButton.setMnemonic(GuiActivator.getResources().getI18nMnemonic("service.gui.CANCEL"));

    okButton.addActionListener(this);
    cancelButton.addActionListener(this);

    buttonsPanel.add(okButton);
    buttonsPanel.add(cancelButton);

    JPanel mainPanel = new TransparentPanel(new GridBagLayout());
    mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10));

    GridBagConstraints mainPanelConstraints = new GridBagConstraints();
    mainPanelConstraints.anchor = GridBagConstraints.NORTH;
    mainPanelConstraints.fill = GridBagConstraints.BOTH;
    mainPanelConstraints.gridx = 0;
    mainPanelConstraints.gridy = 0;
    mainPanelConstraints.weightx = 1;
    mainPanelConstraints.weighty = 1;
    mainPanel.add(messagePanel, mainPanelConstraints);

    mainPanelConstraints.anchor = GridBagConstraints.SOUTHEAST;
    mainPanelConstraints.fill = GridBagConstraints.NONE;
    mainPanelConstraints.gridy = 1;
    mainPanelConstraints.weightx = 0;
    mainPanelConstraints.weighty = 0;
    mainPanel.add(buttonsPanel, mainPanelConstraints);

    this.getContentPane().add(mainPanel);
  }

  /**
   * Handles the <tt>ActionEvent</tt>. In order to change the status message with the new one calls
   * the <tt>PublishStatusMessageThread</tt>.
   *
   * @param e the event that notified us of the action
   */
  public void actionPerformed(ActionEvent e) {
    JButton button = (JButton) e.getSource();
    String name = button.getName();

    if (name.equals("ok")) {
      new PublishStatusMessageThread(messageTextField.getText()).start();
    }

    this.dispose();
  }

  /** Requests the focus in the text field contained in this dialog. */
  public void requestFocusInField() {
    this.messageTextField.requestFocus();
  }

  /** This class allow to use a thread to change the presence status message. */
  private class PublishStatusMessageThread extends Thread {
    private String message;

    private PresenceStatus currentStatus;

    public PublishStatusMessageThread(String message) {
      this.message = message;

      this.currentStatus = presenceOpSet.getPresenceStatus();
    }

    public void run() {
      try {
        presenceOpSet.publishPresenceStatus(currentStatus, message);
      } catch (IllegalArgumentException e1) {

        logger.error("Error - changing status", e1);
      } catch (IllegalStateException e1) {

        logger.error("Error - changing status", e1);
      } catch (OperationFailedException e1) {

        if (e1.getErrorCode() == OperationFailedException.GENERAL_ERROR) {
          logger.error("General error occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.NETWORK_FAILURE) {
          logger.error("Network failure occured while " + "publishing presence status.", e1);
        } else if (e1.getErrorCode() == OperationFailedException.PROVIDER_NOT_REGISTERED) {
          logger.error("Protocol provider must be" + "registered in order to change status.", e1);
        }
      }
    }
  }

  /**
   * Artificially clicks the cancel button when this panel is escaped.
   *
   * @param isEscaped indicates if this dialog is closed by the Esc shortcut
   */
  protected void close(boolean isEscaped) {
    if (isEscaped) cancelButton.doClick();
  }

  /** Reloads icon. */
  public void loadSkin() {
    if (messagePanel != null) {
      for (Component component : messagePanel.getComponents()) {
        if (component instanceof ImageCanvas) {
          ImageCanvas cmp = (ImageCanvas) component;
          cmp.setImage(ImageLoader.getImage(ImageLoader.RENAME_DIALOG_ICON));
        }
      }
    }
  }
}
/**
 * The <tt>GibberishAccountRegistrationWizard</tt> is an implementation of the
 * <tt>AccountRegistrationWizard</tt> for the Gibberish protocol. It allows the user to create and
 * configure a new Gibberish account.
 *
 * @author Emil Ivov
 */
public class GibberishAccountRegistrationWizard extends DesktopAccountRegistrationWizard {
  private final Logger logger = Logger.getLogger(GibberishAccountRegistrationWizard.class);

  /** The first page of the gibberish account registration wizard. */
  private FirstWizardPage firstWizardPage;

  /** The object that we use to store details on an account that we will be creating. */
  private GibberishAccountRegistration registration = new GibberishAccountRegistration();

  private ProtocolProviderService protocolProvider;

  /**
   * Creates an instance of <tt>GibberishAccountRegistrationWizard</tt>.
   *
   * @param wizardContainer the wizard container, where this wizard is added
   */
  public GibberishAccountRegistrationWizard(WizardContainer wizardContainer) {
    setWizardContainer(wizardContainer);

    wizardContainer.setFinishButtonText(Resources.getString("service.gui.SIGN_IN"));
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getIcon</code> method. Returns the icon to be
   * used for this wizard.
   *
   * @return byte[]
   */
  public byte[] getIcon() {
    return Resources.getImage(Resources.GIBBERISH_LOGO);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getPageImage</code> method. Returns the image
   * used to decorate the wizard page
   *
   * @return byte[] the image used to decorate the wizard page
   */
  public byte[] getPageImage() {
    return Resources.getImage(Resources.PAGE_IMAGE);
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolName</code> method. Returns the
   * protocol name for this wizard.
   *
   * @return String
   */
  public String getProtocolName() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_NAME");
  }

  /**
   * Implements the <code>AccountRegistrationWizard.getProtocolDescription
   * </code> method. Returns the description of the protocol for this wizard.
   *
   * @return String
   */
  public String getProtocolDescription() {
    return Resources.getString("plugin.gibberishaccregwizz.PROTOCOL_DESCRIPTION");
  }

  /**
   * Returns the set of pages contained in this wizard.
   *
   * @return Iterator
   */
  public Iterator<WizardPage> getPages() {
    java.util.List<WizardPage> pages = new ArrayList<WizardPage>();
    firstWizardPage = new FirstWizardPage(this);

    pages.add(firstWizardPage);

    return pages.iterator();
  }

  /**
   * Returns the set of data that user has entered through this wizard.
   *
   * @return Iterator
   */
  public Iterator<Map.Entry<String, String>> getSummary() {
    Map<String, String> summaryTable = new Hashtable<String, String>();

    summaryTable.put("User ID", registration.getUserID());

    return summaryTable.entrySet().iterator();
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin() throws OperationFailedException {
    firstWizardPage.commitPage();

    return signin(registration.getUserID(), null);
  }

  /**
   * Defines the operations that will be executed when the user clicks on the wizard "Signin"
   * button.
   *
   * @param userName the user name to sign in with
   * @param password the password to sign in with
   * @return the created <tt>ProtocolProviderService</tt> corresponding to the new account
   * @throws OperationFailedException if the operation didn't succeed
   */
  public ProtocolProviderService signin(String userName, String password)
      throws OperationFailedException {
    ProtocolProviderFactory factory =
        GibberishAccRegWizzActivator.getGibberishProtocolProviderFactory();

    return this.installAccount(factory, userName);
  }

  /**
   * Creates an account for the given user and password.
   *
   * @param providerFactory the ProtocolProviderFactory which will create the account
   * @param user the user identifier
   * @return the <tt>ProtocolProviderService</tt> for the new account.
   */
  public ProtocolProviderService installAccount(
      ProtocolProviderFactory providerFactory, String user) throws OperationFailedException {
    Hashtable<String, String> accountProperties = new Hashtable<String, String>();

    accountProperties.put(
        ProtocolProviderFactory.ACCOUNT_ICON_PATH,
        "resources/images/protocol/gibberish/gibberish32x32.png");

    if (registration.isRememberPassword()) {
      accountProperties.put(ProtocolProviderFactory.PASSWORD, registration.getPassword());
    }

    if (isModification()) {
      providerFactory.uninstallAccount(protocolProvider.getAccountID());
      this.protocolProvider = null;
      setModification(false);
    }

    try {
      AccountID accountID = providerFactory.installAccount(user, accountProperties);

      ServiceReference serRef = providerFactory.getProviderForAccount(accountID);

      protocolProvider =
          (ProtocolProviderService) GibberishAccRegWizzActivator.bundleContext.getService(serRef);
    } catch (IllegalStateException exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Account already exists.", OperationFailedException.IDENTIFICATION_CONFLICT);
    } catch (Exception exc) {
      logger.warn(exc.getMessage());

      throw new OperationFailedException(
          "Failed to add account", OperationFailedException.GENERAL_ERROR);
    }

    return protocolProvider;
  }

  /**
   * Fills the UserID and Password fields in this panel with the data comming from the given
   * protocolProvider.
   *
   * @param protocolProvider The <tt>ProtocolProviderService</tt> to load the data from.
   */
  public void loadAccount(ProtocolProviderService protocolProvider) {
    setModification(true);

    this.protocolProvider = protocolProvider;

    this.registration = new GibberishAccountRegistration();

    this.firstWizardPage.loadAccount(protocolProvider);
  }

  /**
   * Returns the registration object, which will store all the data through the wizard.
   *
   * @return the registration object, which will store all the data through the wizard
   */
  public GibberishAccountRegistration getRegistration() {
    return registration;
  }

  /**
   * Returns the size of this wizard.
   *
   * @return the size of this wizard
   */
  public Dimension getSize() {
    return new Dimension(600, 500);
  }

  /**
   * Returns the identifier of the page to show first in the wizard.
   *
   * @return the identifier of the page to show first in the wizard.
   */
  public Object getFirstPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns the identifier of the page to show last in the wizard.
   *
   * @return the identifier of the page to show last in the wizard.
   */
  public Object getLastPageIdentifier() {
    return firstWizardPage.getIdentifier();
  }

  /**
   * Returns an example string, which should indicate to the user how the user name should look
   * like.
   *
   * @return an example string, which should indicate to the user how the user name should look
   *     like.
   */
  public String getUserNameExample() {
    return FirstWizardPage.USER_NAME_EXAMPLE;
  }

  /**
   * Indicates whether this wizard enables the simple "sign in" form shown when the user opens the
   * application for the first time. The simple "sign in" form allows user to configure her account
   * in one click, just specifying her username and password and leaving any other configuration as
   * by default.
   *
   * @return <code>true</code> if the simple "Sign in" form is enabled or <code>false</code>
   *     otherwise.
   */
  public boolean isSimpleFormEnabled() {
    return false;
  }

  /**
   * Returns a simple account registration form that would be the first form shown to the user. Only
   * if the user needs more settings she'll choose to open the advanced wizard, consisted by all
   * pages.
   *
   * @param isCreateAccount indicates if the simple form should be opened as a create account form
   *     or as a login form
   * @return a simple account registration form
   */
  public Object getSimpleForm(boolean isCreateAccount) {
    firstWizardPage = new FirstWizardPage(this);
    return firstWizardPage.getSimpleForm();
  }
}
/**
 * The advanced configuration panel.
 *
 * @author Yana Stamcheva
 */
public class AdvancedConfigurationPanel extends TransparentPanel
    implements ConfigurationForm, ConfigurationContainer, ServiceListener, ListSelectionListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  /** The <tt>Logger</tt> used by this <tt>AdvancedConfigurationPanel</tt> for logging output. */
  private final Logger logger = Logger.getLogger(AdvancedConfigurationPanel.class);

  /** The configuration list. */
  private final JList configList = new JList();

  /** The center panel. */
  private final JPanel centerPanel = new TransparentPanel(new BorderLayout());

  /** Creates an instance of the <tt>AdvancedConfigurationPanel</tt>. */
  public AdvancedConfigurationPanel() {
    super(new BorderLayout(10, 0));

    initList();

    centerPanel.setPreferredSize(new Dimension(500, 500));

    add(centerPanel, BorderLayout.CENTER);
  }

  /** Initializes the config list. */
  private void initList() {
    configList.setModel(new DefaultListModel());
    configList.setCellRenderer(new ConfigListCellRenderer());
    configList.addListSelectionListener(this);
    configList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    JScrollPane configScrollList = new JScrollPane();

    configScrollList.getVerticalScrollBar().setUnitIncrement(30);

    configScrollList.getViewport().add(configList);

    add(configScrollList, BorderLayout.WEST);

    String osgiFilter =
        "(" + ConfigurationForm.FORM_TYPE + "=" + ConfigurationForm.ADVANCED_TYPE + ")";
    ServiceReference[] confFormsRefs = null;
    try {
      confFormsRefs =
          AdvancedConfigActivator.bundleContext.getServiceReferences(
              ConfigurationForm.class.getName(), osgiFilter);
    } catch (InvalidSyntaxException ex) {
    }

    if (confFormsRefs != null) {
      for (int i = 0; i < confFormsRefs.length; i++) {
        ConfigurationForm form =
            (ConfigurationForm) AdvancedConfigActivator.bundleContext.getService(confFormsRefs[i]);

        if (form.isAdvanced()) this.addConfigForm(form);
      }
    }
  }

  /**
   * Shows on the right the configuration form given by the given <tt>ConfigFormDescriptor</tt>.
   *
   * @param configForm the configuration form to show
   */
  private void showFormContent(ConfigurationForm configForm) {
    this.centerPanel.removeAll();

    JComponent configFormPanel = (JComponent) configForm.getForm();

    configFormPanel.setOpaque(false);

    this.centerPanel.add(configFormPanel, BorderLayout.CENTER);

    this.centerPanel.revalidate();
    this.centerPanel.repaint();
  }

  /**
   * Handles registration of a new configuration form.
   *
   * @param event the <tt>ServiceEvent</tt> that notified us
   */
  public void serviceChanged(ServiceEvent event) {
    Object sService = AdvancedConfigActivator.bundleContext.getService(event.getServiceReference());

    // we don't care if the source service is not a configuration form
    if (!(sService instanceof ConfigurationForm)) return;

    ConfigurationForm configForm = (ConfigurationForm) sService;

    /*
     * This AdvancedConfigurationPanel is an advanced ConfigurationForm so
     * don't try to add it to itself.
     */
    if ((configForm == this) || !configForm.isAdvanced()) return;

    switch (event.getType()) {
      case ServiceEvent.REGISTERED:
        if (logger.isInfoEnabled())
          logger.info("Handling registration of a new Configuration Form.");

        this.addConfigForm(configForm);
        break;

      case ServiceEvent.UNREGISTERING:
        this.removeConfigForm(configForm);
        break;
    }
  }

  /**
   * Adds a new <tt>ConfigurationForm</tt> to this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to add.
   */
  public void addConfigForm(ConfigurationForm configForm) {
    if (configForm == null) throw new IllegalArgumentException("configForm");

    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    int i = 0;
    int count = listModel.getSize();
    int configFormIndex = configForm.getIndex();
    for (; i < count; i++) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (configFormIndex < form.getIndex()) break;
    }
    listModel.add(i, configForm);
  }

  /**
   * Implements <code>ApplicationWindow.show</code> method.
   *
   * @param isVisible specifies whether the frame is to be visible or not.
   */
  @Override
  public void setVisible(boolean isVisible) {
    if (isVisible && configList.getSelectedIndex() < 0) {
      this.configList.setSelectedIndex(0);
    }
    super.setVisible(isVisible);
  }

  /**
   * Removes a <tt>ConfigurationForm</tt> from this list.
   *
   * @param configForm The <tt>ConfigurationForm</tt> to remove.
   */
  public void removeConfigForm(ConfigurationForm configForm) {
    DefaultListModel listModel = (DefaultListModel) configList.getModel();

    for (int count = listModel.getSize(), i = count - 1; i >= 0; i--) {
      ConfigurationForm form = (ConfigurationForm) listModel.get(i);

      if (form.equals(configForm)) {
        listModel.remove(i);
        /*
         * TODO We may just consider not allowing duplicates on addition
         * and then break here.
         */
      }
    }
  }

  /** A custom cell renderer that represents a <tt>ConfigurationForm</tt>. */
  private class ConfigListCellRenderer extends DefaultListCellRenderer {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    private boolean isSelected = false;

    private final Color selectedColor =
        new Color(
            AdvancedConfigActivator.getResources().getColor("service.gui.LIST_SELECTION_COLOR"));

    /**
     * Creates an instance of <tt>ConfigListCellRenderer</tt> and specifies that this renderer is
     * transparent.
     */
    public ConfigListCellRenderer() {
      this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      this.setOpaque(false);
    }

    /**
     * Returns the component representing the cell given by parameters.
     *
     * @param list the parent list
     * @param value the value of the cell
     * @param index the index of the cell
     * @param isSelected indicates if the cell is selected
     * @param cellHasFocus indicates if the cell has the focus
     * @return the component representing the cell
     */
    public Component getListCellRendererComponent(
        JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      ConfigurationForm configForm = (ConfigurationForm) value;

      this.isSelected = isSelected;
      this.setText(configForm.getTitle());

      return this;
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    public void paintComponent(Graphics g) {
      Graphics g2 = g.create();
      try {
        internalPaintComponent(g2);
      } finally {
        g2.dispose();
      }
      super.paintComponent(g);
    }

    /**
     * Paint a background for all groups and a round blue border and background when a cell is
     * selected.
     *
     * @param g the <tt>Graphics</tt> object
     */
    private void internalPaintComponent(Graphics g) {
      AntialiasingManager.activateAntialiasing(g);

      Graphics2D g2 = (Graphics2D) g;

      if (isSelected) {
        g2.setColor(selectedColor);
        g2.fillRect(0, 0, this.getWidth(), this.getHeight());
      }
    }
  }

  /**
   * Called when user selects a component in the list of configuration forms.
   *
   * @param e the <tt>ListSelectionEvent</tt>
   */
  public void valueChanged(ListSelectionEvent e) {
    if (!e.getValueIsAdjusting()) {
      ConfigurationForm configForm = (ConfigurationForm) configList.getSelectedValue();

      if (configForm != null) showFormContent(configForm);
    }
  }

  /**
   * Selects the given <tt>ConfigurationForm</tt>.
   *
   * @param configForm the <tt>ConfigurationForm</tt> to select
   */
  public void setSelected(ConfigurationForm configForm) {
    configList.setSelectedValue(configForm, true);
  }

  /**
   * Returns the title of the form.
   *
   * @return the title of the form
   */
  public String getTitle() {
    return AdvancedConfigActivator.getResources().getI18NString("service.gui.ADVANCED");
  }

  /**
   * Returns the icon of the form.
   *
   * @return a byte array containing the icon of the form
   */
  public byte[] getIcon() {
    return AdvancedConfigActivator.getResources()
        .getImageInBytes("plugin.advancedconfig.PLUGIN_ICON");
  }

  /**
   * Returns the form component.
   *
   * @return the form component
   */
  public Object getForm() {
    return this;
  }

  /**
   * Returns the index of the form in its parent container.
   *
   * @return the index of the form in its parent container
   */
  public int getIndex() {
    return 300;
  }

  /**
   * Indicates if the form is an advanced form.
   *
   * @return <tt>true</tt> to indicate that this is an advanced form, otherwise returns
   *     <tt>false</tt>
   */
  public boolean isAdvanced() {
    return false;
  }

  /**
   * Validates the currently selected configuration form. This method is meant to be used by
   * configuration forms the re-validate when a new component has been added or size has changed.
   */
  public void validateCurrentForm() {}
}
Exemple #16
0
/**
 * A meta-class to handle all logging and input-related console improvements. Portions are heavily
 * based on CraftBukkit.
 */
public final class ConsoleManager {

  private static final String CONSOLE_DATE = "HH:mm:ss";
  private static final String FILE_DATE = "yyyy/MM/dd HH:mm:ss";
  private static final Logger logger = Logger.getLogger("");

  private final GlowServer server;

  private ConsoleReader reader;
  private ConsoleCommandSender sender;
  private ConsoleHandler consoleHandler;

  private JFrame jFrame = null;
  private JTerminal jTerminal = null;
  private JTextField jInput = null;

  private boolean running = true;
  private boolean jLine = false;

  public ConsoleManager(GlowServer server) {
    this.server = server;

    // install Ansi code handler, which makes colors work on Windows
    AnsiConsole.systemInstall();

    for (Handler h : logger.getHandlers()) {
      logger.removeHandler(h);
    }

    // used until/unless gui is created
    consoleHandler = new FancyConsoleHandler();
    // consoleHandler.setFormatter(new DateOutputFormatter(CONSOLE_DATE));
    logger.addHandler(consoleHandler);

    // todo: why is this here?
    Runtime.getRuntime().addShutdownHook(new ServerShutdownThread());

    // reader must be initialized before standard streams are changed
    try {
      reader = new ConsoleReader();
    } catch (IOException ex) {
      logger.log(Level.SEVERE, "Exception initializing console reader", ex);
    }
    reader.addCompleter(new CommandCompleter());

    // set system output streams
    System.setOut(new PrintStream(new LoggerOutputStream(Level.INFO), true));
    System.setErr(new PrintStream(new LoggerOutputStream(Level.WARNING), true));
  }

  public ConsoleCommandSender getSender() {
    return sender;
  }

  public void startGui() {
    JTerminalListener listener = new JTerminalListener();

    jFrame = new JFrame("Glowstone");
    jTerminal = new JTerminal();
    jInput =
        new JTextField(80) {
          @Override
          public void setBorder(Border border) {}
        };
    jInput.paint(null);
    jInput.setFont(new Font("Monospaced", Font.PLAIN, 12));
    jInput.setBackground(Color.BLACK);
    jInput.setForeground(Color.WHITE);
    jInput.setMargin(new Insets(0, 0, 0, 0));
    jInput.addKeyListener(listener);

    JLabel caret = new JLabel("> ");
    caret.setFont(new Font("Monospaced", Font.PLAIN, 12));
    caret.setForeground(Color.WHITE);

    JPanel ipanel = new JPanel();
    ipanel.add(caret, BorderLayout.WEST);
    ipanel.add(jInput, BorderLayout.EAST);
    ipanel.setBorder(BorderFactory.createEmptyBorder());
    ipanel.setBackground(Color.BLACK);
    ipanel.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
    ipanel.setSize(jTerminal.getWidth(), ipanel.getHeight());

    jFrame.getContentPane().add(jTerminal, BorderLayout.NORTH);
    jFrame.getContentPane().add(ipanel, BorderLayout.SOUTH);
    jFrame.addWindowListener(listener);
    jFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    jFrame.setLocationRelativeTo(null);
    jFrame.pack();
    jFrame.setVisible(true);

    sender = new ColoredCommandSender();
    logger.removeHandler(consoleHandler);
    logger.addHandler(
        new StreamHandler(new TerminalOutputStream(), new DateOutputFormatter(CONSOLE_DATE)));
  }

  public void startConsole(boolean jLine) {
    this.jLine = jLine;

    sender = new ColoredCommandSender();
    Thread thread = new ConsoleCommandThread();
    thread.setName("ConsoleCommandThread");
    thread.setDaemon(true);
    thread.start();

    /*logger.removeHandler(consoleHandler);
    consoleHandler = new FancyConsoleHandler();
    consoleHandler.setFormatter(new DateOutputFormatter(CONSOLE_DATE));
    logger.addHandler(consoleHandler);*/
  }

  public void startFile(String logfile) {
    File parent = new File(logfile).getParentFile();
    if (!parent.isDirectory() && !parent.mkdirs()) {
      logger.warning("Could not create log folder: " + parent);
    }
    Handler fileHandler = new RotatingFileHandler(logfile);
    fileHandler.setFormatter(new DateOutputFormatter(FILE_DATE));
    logger.addHandler(fileHandler);
  }

  public void stop() {
    running = false;
    for (Handler handler : logger.getHandlers()) {
      handler.flush();
      handler.close();
    }
    if (jFrame != null) {
      jFrame.dispose();
    }
  }

  private String colorize(String string) {
    if (string.indexOf(ChatColor.COLOR_CHAR) < 0) {
      return string; // no colors in the message
    } else if ((!jLine || !reader.getTerminal().isAnsiSupported()) && jTerminal == null) {
      return ChatColor.stripColor(string); // color not supported
    } else {
      return string
              .replace(ChatColor.RED.toString(), "\033[1;31m")
              .replace(ChatColor.YELLOW.toString(), "\033[1;33m")
              .replace(ChatColor.GREEN.toString(), "\033[1;32m")
              .replace(ChatColor.AQUA.toString(), "\033[1;36m")
              .replace(ChatColor.BLUE.toString(), "\033[1;34m")
              .replace(ChatColor.LIGHT_PURPLE.toString(), "\033[1;35m")
              .replace(ChatColor.BLACK.toString(), "\033[0;0m")
              .replace(ChatColor.DARK_GRAY.toString(), "\033[1;30m")
              .replace(ChatColor.DARK_RED.toString(), "\033[0;31m")
              .replace(ChatColor.GOLD.toString(), "\033[0;33m")
              .replace(ChatColor.DARK_GREEN.toString(), "\033[0;32m")
              .replace(ChatColor.DARK_AQUA.toString(), "\033[0;36m")
              .replace(ChatColor.DARK_BLUE.toString(), "\033[0;34m")
              .replace(ChatColor.DARK_PURPLE.toString(), "\033[0;35m")
              .replace(ChatColor.GRAY.toString(), "\033[0;37m")
              .replace(ChatColor.WHITE.toString(), "\033[1;37m")
          + "\033[0m";
    }
  }

  private class CommandCompleter implements Completer {
    public int complete(final String buffer, int cursor, List<CharSequence> candidates) {
      try {
        List<String> completions =
            server
                .getScheduler()
                .syncIfNeeded(
                    new Callable<List<String>>() {
                      public List<String> call() throws Exception {
                        return server.getCommandMap().tabComplete(sender, buffer);
                      }
                    });
        if (completions == null) {
          return cursor; // no completions
        }
        candidates.addAll(completions);

        // location to position the cursor at (before autofilling takes place)
        return buffer.lastIndexOf(' ') + 1;
      } catch (Throwable t) {
        logger.log(Level.WARNING, "Error while tab completing", t);
        return cursor;
      }
    }
  }

  private class ConsoleCommandThread extends Thread {
    @Override
    public void run() {
      String command = "";
      while (running) {
        try {
          if (jLine) {
            command = reader.readLine(">", null);
          } else {
            command = reader.readLine();
          }

          if (command == null || command.trim().length() == 0) continue;

          server.getScheduler().runTask(null, new CommandTask(command.trim()));
        } catch (CommandException ex) {
          logger.log(Level.WARNING, "Exception while executing command: " + command, ex);
        } catch (Exception ex) {
          logger.log(Level.SEVERE, "Error while reading commands", ex);
        }
      }
    }
  }

  private class ServerShutdownThread extends Thread {
    @Override
    public void run() {
      server.shutdown();
    }
  }

  private class CommandTask implements Runnable {
    private final String command;

    public CommandTask(String command) {
      this.command = command;
    }

    public void run() {
      server.dispatchCommand(sender, EventFactory.onServerCommand(sender, command).getCommand());
    }
  }

  private class ColoredCommandSender implements ConsoleCommandSender {
    private final PermissibleBase perm = new PermissibleBase(this);

    ////////////////////////////////////////////////////////////////////////
    // CommandSender

    public String getName() {
      return "CONSOLE";
    }

    public void sendMessage(String text) {
      server.getLogger().info(text);
    }

    public void sendMessage(String[] strings) {
      for (String line : strings) {
        sendMessage(line);
      }
    }

    public GlowServer getServer() {
      return server;
    }

    public boolean isOp() {
      return true;
    }

    public void setOp(boolean value) {
      throw new UnsupportedOperationException("Cannot change operator status of server console");
    }

    ////////////////////////////////////////////////////////////////////////
    // Permissible

    public boolean isPermissionSet(String name) {
      return perm.isPermissionSet(name);
    }

    public boolean isPermissionSet(Permission perm) {
      return this.perm.isPermissionSet(perm);
    }

    public boolean hasPermission(String name) {
      return perm.hasPermission(name);
    }

    public boolean hasPermission(Permission perm) {
      return this.perm.hasPermission(perm);
    }

    public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) {
      return perm.addAttachment(plugin, name, value);
    }

    public PermissionAttachment addAttachment(Plugin plugin) {
      return perm.addAttachment(plugin);
    }

    public PermissionAttachment addAttachment(
        Plugin plugin, String name, boolean value, int ticks) {
      return perm.addAttachment(plugin, name, value, ticks);
    }

    public PermissionAttachment addAttachment(Plugin plugin, int ticks) {
      return perm.addAttachment(plugin, ticks);
    }

    public void removeAttachment(PermissionAttachment attachment) {
      perm.removeAttachment(attachment);
    }

    public void recalculatePermissions() {
      perm.recalculatePermissions();
    }

    public Set<PermissionAttachmentInfo> getEffectivePermissions() {
      return perm.getEffectivePermissions();
    }

    ////////////////////////////////////////////////////////////////////////
    // Conversable

    @Override
    public boolean isConversing() {
      return false;
    }

    @Override
    public void acceptConversationInput(String input) {}

    @Override
    public boolean beginConversation(Conversation conversation) {
      return false;
    }

    @Override
    public void abandonConversation(Conversation conversation) {}

    @Override
    public void abandonConversation(
        Conversation conversation, ConversationAbandonedEvent details) {}

    @Override
    public void sendRawMessage(String message) {}
  }

  private static class LoggerOutputStream extends ByteArrayOutputStream {
    private final String separator = System.getProperty("line.separator");
    private final Level level;

    public LoggerOutputStream(Level level) {
      super();
      this.level = level;
    }

    @Override
    public synchronized void flush() throws IOException {
      super.flush();
      String record = this.toString();
      super.reset();

      if (record.length() > 0 && !record.equals(separator)) {
        logger.logp(level, "LoggerOutputStream", "log" + level, record);
      }
    }
  }

  private class FancyConsoleHandler extends ConsoleHandler {
    public FancyConsoleHandler() {
      setFormatter(new DateOutputFormatter(CONSOLE_DATE));
      setOutputStream(System.out);
    }

    @Override
    public synchronized void flush() {
      try {
        if (jLine) {
          reader.print(ConsoleReader.RESET_LINE + "");
          reader.flush();
          super.flush();
          try {
            reader.drawLine();
          } catch (Throwable ex) {
            reader.getCursorBuffer().clear();
          }
          reader.flush();
        } else {
          super.flush();
        }
      } catch (IOException ex) {
        logger.log(Level.SEVERE, "I/O exception flushing console output", ex);
      }
    }
  }

  private static class RotatingFileHandler extends StreamHandler {
    private final SimpleDateFormat dateFormat;
    private final String template;
    private final boolean rotate;
    private String filename;

    public RotatingFileHandler(String template) {
      this.template = template;
      rotate = template.contains("%D");
      dateFormat = new SimpleDateFormat("yyyy-MM-dd");
      filename = calculateFilename();
      updateOutput();
    }

    private void updateOutput() {
      try {
        setOutputStream(new FileOutputStream(filename, true));
      } catch (IOException ex) {
        logger.log(Level.SEVERE, "Unable to open " + filename + " for writing", ex);
      }
    }

    private String calculateFilename() {
      return template.replace("%D", dateFormat.format(new Date()));
    }

    @Override
    public synchronized void publish(LogRecord record) {
      if (!isLoggable(record)) {
        return;
      }
      super.publish(record);
      flush();
    }

    @Override
    public synchronized void flush() {
      if (rotate) {
        String newFilename = calculateFilename();
        if (!filename.equals(newFilename)) {
          filename = newFilename;
          logger.log(Level.INFO, "Log rotating to {0}...", filename);
          updateOutput();
        }
      }
      super.flush();
    }
  }

  private class DateOutputFormatter extends Formatter {
    private final SimpleDateFormat date;

    public DateOutputFormatter(String pattern) {
      this.date = new SimpleDateFormat(pattern);
    }

    @Override
    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    public String format(LogRecord record) {
      StringBuilder builder = new StringBuilder();

      builder.append(date.format(record.getMillis()));
      builder.append(" [");
      builder.append(record.getLevel().getLocalizedName().toUpperCase());
      builder.append("] ");
      builder.append(colorize(formatMessage(record)));
      builder.append('\n');

      if (record.getThrown() != null) {
        StringWriter writer = new StringWriter();
        record.getThrown().printStackTrace(new PrintWriter(writer));
        builder.append(writer.toString());
      }

      return builder.toString();
    }
  }

  private class JTerminalListener implements WindowListener, KeyListener {
    public void windowOpened(WindowEvent e) {}

    public void windowIconified(WindowEvent e) {}

    public void windowDeiconified(WindowEvent e) {}

    public void windowActivated(WindowEvent e) {}

    public void windowDeactivated(WindowEvent e) {}

    public void windowClosed(WindowEvent e) {}

    public void keyPressed(KeyEvent e) {}

    public void keyReleased(KeyEvent e) {}

    public void windowClosing(WindowEvent e) {
      server.shutdown();
    }

    public void keyTyped(KeyEvent e) {
      if (e.getKeyChar() == '\n') {
        String command = jInput.getText().trim();
        if (command.length() > 0) {
          server.getScheduler().scheduleSyncDelayedTask(null, new CommandTask(command));
        }
        jInput.setText("");
      }
    }
  }

  private class TerminalOutputStream extends ByteArrayOutputStream {
    private final String separator = System.getProperty("line.separator");

    @Override
    public synchronized void flush() throws IOException {
      super.flush();
      String record = this.toString();
      super.reset();

      if (record.length() > 0 && !record.equals(separator)) {
        jTerminal.print(record);
        jFrame.repaint();
      }
    }
  }
}
Exemple #17
0
/**
 * The <tt>CallManager</tt> is the one that handles calls. It contains also the "Call" and "Hangup"
 * buttons panel. Here are handles incoming and outgoing calls from and to the call operation set.
 *
 * @author Yana Stamcheva
 */
public class CallManager extends JPanel
    implements ActionListener, CallListener, ListSelectionListener, ChangeListener {
  private Logger logger = Logger.getLogger(CallManager.class.getName());

  private CallComboBox phoneNumberCombo;

  private JPanel comboPanel = new JPanel(new BorderLayout());

  private JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));

  private JLabel callViaLabel = new JLabel(Messages.getI18NString("callVia").getText() + " ");

  private JPanel callViaPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 0, 4));

  private AccountSelectorBox accountSelectorBox;

  private SIPCommButton callButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_BUTTON_BG),
          ImageLoader.getImage(ImageLoader.CALL_ROLLOVER_BUTTON_BG),
          null,
          ImageLoader.getImage(ImageLoader.CALL_BUTTON_PRESSED_BG));

  private SIPCommButton hangupButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_BG),
          ImageLoader.getImage(ImageLoader.HANGUP_ROLLOVER_BUTTON_BG),
          null,
          ImageLoader.getImage(ImageLoader.HANGUP_BUTTON_PRESSED_BG));

  private SIPCommButton minimizeButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_BUTTON),
          ImageLoader.getImage(ImageLoader.CALL_PANEL_MINIMIZE_ROLLOVER_BUTTON));

  private SIPCommButton restoreButton =
      new SIPCommButton(
          ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_BUTTON),
          ImageLoader.getImage(ImageLoader.CALL_PANEL_RESTORE_ROLLOVER_BUTTON));

  private JPanel minimizeButtonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));

  private MainFrame mainFrame;

  private Hashtable activeCalls = new Hashtable();

  private boolean isCallMetaContact;

  private Hashtable removeCallTimers = new Hashtable();

  private ProtocolProviderService selectedCallProvider;

  /**
   * Creates an instance of <tt>CallManager</tt>.
   *
   * @param mainFrame The main application window.
   */
  public CallManager(MainFrame mainFrame) {
    super(new BorderLayout());

    this.mainFrame = mainFrame;

    this.phoneNumberCombo = new CallComboBox(this);

    this.accountSelectorBox = new AccountSelectorBox(this);

    this.buttonsPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));

    this.comboPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 0, 5));

    this.init();
  }

  /** Initializes and constructs this panel. */
  private void init() {
    this.phoneNumberCombo.setEditable(true);

    this.callViaPanel.add(callViaLabel);
    this.callViaPanel.add(accountSelectorBox);

    this.comboPanel.add(phoneNumberCombo, BorderLayout.CENTER);

    this.callButton.setName("call");
    this.hangupButton.setName("hangup");
    this.minimizeButton.setName("minimize");
    this.restoreButton.setName("restore");

    this.minimizeButton.setToolTipText(
        Messages.getI18NString("hideCallPanel").getText() + " Ctrl - H");
    this.restoreButton.setToolTipText(
        Messages.getI18NString("showCallPanel").getText() + " Ctrl - H");

    this.callButton.addActionListener(this);
    this.hangupButton.addActionListener(this);
    this.minimizeButton.addActionListener(this);
    this.restoreButton.addActionListener(this);

    this.buttonsPanel.add(callButton);
    this.buttonsPanel.add(hangupButton);

    this.callButton.setEnabled(false);

    this.hangupButton.setEnabled(false);

    this.add(minimizeButtonPanel, BorderLayout.SOUTH);

    this.setCallPanelVisible(ConfigurationManager.isCallPanelShown());
  }

  /**
   * Handles the <tt>ActionEvent</tt> generated when user presses one of the buttons in this panel.
   */
  public void actionPerformed(ActionEvent evt) {
    JButton button = (JButton) evt.getSource();
    String buttonName = button.getName();

    if (buttonName.equals("call")) {
      Component selectedPanel = mainFrame.getSelectedTab();

      // call button is pressed over an already open call panel
      if (selectedPanel != null
          && selectedPanel instanceof CallPanel
          && ((CallPanel) selectedPanel).getCall().getCallState()
              == CallState.CALL_INITIALIZATION) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Iterator participantPanels = callPanel.getParticipantsPanels();

        while (participantPanels.hasNext()) {
          CallParticipantPanel panel = (CallParticipantPanel) participantPanels.next();

          panel.setState("Connecting");
        }

        Call call = callPanel.getCall();

        answerCall(call);
      }
      // call button is pressed over the call list
      else if (selectedPanel != null
          && selectedPanel instanceof CallListPanel
          && ((CallListPanel) selectedPanel).getCallList().getSelectedIndex() != -1) {

        CallListPanel callListPanel = (CallListPanel) selectedPanel;

        GuiCallParticipantRecord callRecord =
            (GuiCallParticipantRecord) callListPanel.getCallList().getSelectedValue();

        String stringContact = callRecord.getParticipantName();

        createCall(stringContact);
      }
      // call button is pressed over the contact list
      else if (selectedPanel != null && selectedPanel instanceof ContactListPanel) {
        // call button is pressed when a meta contact is selected
        if (isCallMetaContact) {
          Object[] selectedContacts =
              mainFrame.getContactListPanel().getContactList().getSelectedValues();

          Vector telephonyContacts = new Vector();

          for (int i = 0; i < selectedContacts.length; i++) {

            Object o = selectedContacts[i];

            if (o instanceof MetaContact) {

              Contact contact =
                  ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class);

              if (contact != null) telephonyContacts.add(contact);
              else {
                new ErrorDialog(
                        this.mainFrame,
                        Messages.getI18NString("warning").getText(),
                        Messages.getI18NString(
                                "contactNotSupportingTelephony",
                                new String[] {((MetaContact) o).getDisplayName()})
                            .getText())
                    .showDialog();
              }
            }
          }

          if (telephonyContacts.size() > 0) createCall(telephonyContacts);

        } else if (!phoneNumberCombo.isComboFieldEmpty()) {

          // if no contact is selected checks if the user has chosen
          // or has
          // writen something in the phone combo box

          String stringContact = phoneNumberCombo.getEditor().getItem().toString();

          createCall(stringContact);
        }
      } else if (selectedPanel != null && selectedPanel instanceof DialPanel) {
        String stringContact = phoneNumberCombo.getEditor().getItem().toString();
        createCall(stringContact);
      }
    } else if (buttonName.equalsIgnoreCase("hangup")) {
      Component selectedPanel = this.mainFrame.getSelectedTab();

      if (selectedPanel != null && selectedPanel instanceof CallPanel) {

        NotificationManager.stopSound(NotificationManager.BUSY_CALL);
        NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
        NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);

        CallPanel callPanel = (CallPanel) selectedPanel;

        Call call = callPanel.getCall();

        if (removeCallTimers.containsKey(callPanel)) {
          ((Timer) removeCallTimers.get(callPanel)).stop();
          removeCallTimers.remove(callPanel);
        }

        removeCallPanel(callPanel);

        if (call != null) {
          ProtocolProviderService pps = call.getProtocolProvider();

          OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps);

          Iterator participants = call.getCallParticipants();

          while (participants.hasNext()) {
            try {
              // now we hang up the first call participant in the
              // call
              telephony.hangupCallParticipant((CallParticipant) participants.next());
            } catch (OperationFailedException e) {
              logger.error("Hang up was not successful: " + e);
            }
          }
        }
      }
    } else if (buttonName.equalsIgnoreCase("minimize")) {
      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (!hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(true);

      this.setCallPanelVisible(false);
    } else if (buttonName.equalsIgnoreCase("restore")) {

      JCheckBoxMenuItem hideCallPanelItem =
          mainFrame.getMainMenu().getViewMenu().getHideCallPanelItem();

      if (hideCallPanelItem.isSelected()) hideCallPanelItem.setSelected(false);

      this.setCallPanelVisible(true);
    }
  }

  /** Hides the panel containing call and hangup buttons. */
  public void setCallPanelVisible(boolean isVisible) {
    if (isVisible) {
      this.add(comboPanel, BorderLayout.NORTH);
      this.add(buttonsPanel, BorderLayout.CENTER);

      this.minimizeButtonPanel.removeAll();
      this.minimizeButtonPanel.add(minimizeButton);
    } else {
      this.remove(comboPanel);
      this.remove(buttonsPanel);

      this.minimizeButtonPanel.removeAll();
      this.minimizeButtonPanel.add(restoreButton);

      if (mainFrame.isVisible())
        this.mainFrame.getContactListPanel().getContactList().requestFocus();
    }

    if (ConfigurationManager.isCallPanelShown() != isVisible)
      ConfigurationManager.setShowCallPanel(isVisible);

    this.mainFrame.validate();
  }

  /**
   * Adds the given call account to the list of call via accounts.
   *
   * @param pps the protocol provider service corresponding to the account
   */
  public void addCallAccount(ProtocolProviderService pps) {
    if (accountSelectorBox.getAccountsNumber() > 0) {
      this.comboPanel.add(callViaPanel, BorderLayout.SOUTH);
    }
    accountSelectorBox.addAccount(pps);
  }

  /**
   * Removes the account corresponding to the given protocol provider from the call via selector
   * box.
   *
   * @param pps the protocol provider service to remove
   */
  public void removeCallAccount(ProtocolProviderService pps) {
    this.accountSelectorBox.removeAccount(pps);

    if (accountSelectorBox.getAccountsNumber() < 2) {
      this.comboPanel.remove(callViaPanel);
    }
  }

  /**
   * Returns TRUE if the account corresponding to the given protocol provider is already contained
   * in the call via selector box, otherwise returns FALSE.
   *
   * @param pps the protocol provider service for the account
   * @return TRUE if the account corresponding to the given protocol provider is already contained
   *     in the call via selector box, otherwise returns FALSE
   */
  public boolean containsCallAccount(ProtocolProviderService pps) {
    return accountSelectorBox.containsAccount(pps);
  }

  /**
   * Updates the call via account status.
   *
   * @param pps the protocol provider service for the account
   */
  public void updateCallAccountStatus(ProtocolProviderService pps) {
    accountSelectorBox.updateAccountStatus(pps);
  }

  /**
   * Returns the account selector box.
   *
   * @return the account selector box.
   */
  public AccountSelectorBox getAccountSelectorBox() {
    return accountSelectorBox;
  }

  /**
   * Sets the protocol provider to use for a call.
   *
   * @param provider the protocol provider to use for a call.
   */
  public void setCallProvider(ProtocolProviderService provider) {
    this.selectedCallProvider = provider;
  }

  /**
   * Implements CallListener.incomingCallReceived. When a call is received creates a call panel and
   * adds it to the main tabbed pane and plays the ring phone sound to the user.
   */
  public void incomingCallReceived(CallEvent event) {
    Call sourceCall = event.getSourceCall();

    CallPanel callPanel = new CallPanel(this, sourceCall, GuiCallParticipantRecord.INCOMING_CALL);

    mainFrame.addCallPanel(callPanel);

    if (mainFrame.getState() == JFrame.ICONIFIED) mainFrame.setState(JFrame.NORMAL);

    if (!mainFrame.isVisible()) mainFrame.setVisible(true);

    mainFrame.toFront();

    this.callButton.setEnabled(true);
    this.hangupButton.setEnabled(true);

    NotificationManager.fireNotification(
        NotificationManager.INCOMING_CALL,
        null,
        "Incoming call recived from: " + sourceCall.getCallParticipants().next());

    activeCalls.put(sourceCall, callPanel);

    this.setCallPanelVisible(true);
  }

  /**
   * Implements CallListener.callEnded. Stops sounds that are playing at the moment if there're any.
   * Removes the call panel and disables the hangup button.
   */
  public void callEnded(CallEvent event) {
    Call sourceCall = event.getSourceCall();

    NotificationManager.stopSound(NotificationManager.BUSY_CALL);
    NotificationManager.stopSound(NotificationManager.INCOMING_CALL);
    NotificationManager.stopSound(NotificationManager.OUTGOING_CALL);

    if (activeCalls.get(sourceCall) != null) {
      CallPanel callPanel = (CallPanel) activeCalls.get(sourceCall);

      this.removeCallPanelWait(callPanel);
    }
  }

  public void outgoingCallCreated(CallEvent event) {}

  /**
   * Removes the given call panel tab.
   *
   * @param callPanel the CallPanel to remove
   */
  public void removeCallPanelWait(CallPanel callPanel) {
    Timer timer = new Timer(5000, new RemoveCallPanelListener(callPanel));

    this.removeCallTimers.put(callPanel, timer);

    timer.setRepeats(false);
    timer.start();
  }

  /**
   * Removes the given call panel tab.
   *
   * @param callPanel the CallPanel to remove
   */
  private void removeCallPanel(CallPanel callPanel) {
    if (callPanel.getCall() != null && activeCalls.contains(callPanel.getCall())) {
      this.activeCalls.remove(callPanel.getCall());
    }

    mainFrame.removeCallPanel(callPanel);
    updateButtonsStateAccordingToSelectedPanel();
  }

  /** Removes the given CallPanel from the main tabbed pane. */
  private class RemoveCallPanelListener implements ActionListener {
    private CallPanel callPanel;

    public RemoveCallPanelListener(CallPanel callPanel) {
      this.callPanel = callPanel;
    }

    public void actionPerformed(ActionEvent e) {
      removeCallPanel(callPanel);
    }
  }

  /**
   * Implements ListSelectionListener.valueChanged. Enables or disables call and hangup buttons
   * depending on the selection in the contactlist.
   */
  public void valueChanged(ListSelectionEvent e) {
    Object o = mainFrame.getContactListPanel().getContactList().getSelectedValue();

    if ((e.getFirstIndex() != -1 || e.getLastIndex() != -1) && (o instanceof MetaContact)) {
      setCallMetaContact(true);

      // Switch automatically to the appropriate pps in account selector
      // box and enable callButton if telephony is supported.
      Contact contact = ((MetaContact) o).getDefaultContact(OperationSetBasicTelephony.class);

      if (contact != null) {
        callButton.setEnabled(true);

        if (contact.getProtocolProvider().isRegistered())
          getAccountSelectorBox().setSelected(contact.getProtocolProvider());
      } else {
        callButton.setEnabled(false);
      }
    } else if (phoneNumberCombo.isComboFieldEmpty()) {
      callButton.setEnabled(false);
    }
  }

  /**
   * Implements ChangeListener.stateChanged. Enables the hangup button if ones selects a tab in the
   * main tabbed pane that contains a call panel.
   */
  public void stateChanged(ChangeEvent e) {
    this.updateButtonsStateAccordingToSelectedPanel();

    Component selectedPanel = mainFrame.getSelectedTab();
    if (selectedPanel == null || !(selectedPanel instanceof CallPanel)) {
      Iterator callPanels = activeCalls.values().iterator();

      while (callPanels.hasNext()) {
        CallPanel callPanel = (CallPanel) callPanels.next();

        callPanel.removeDialogs();
      }
    }
  }

  /** Updates call and hangup buttons' states aa */
  private void updateButtonsStateAccordingToSelectedPanel() {
    Component selectedPanel = mainFrame.getSelectedTab();
    if (selectedPanel != null && selectedPanel instanceof CallPanel) {
      this.hangupButton.setEnabled(true);
    } else {
      this.hangupButton.setEnabled(false);
    }
  }

  /**
   * Returns the call button.
   *
   * @return the call button
   */
  public SIPCommButton getCallButton() {
    return callButton;
  }

  /**
   * Returns the hangup button.
   *
   * @return the hangup button
   */
  public SIPCommButton getHangupButton() {
    return hangupButton;
  }

  /**
   * Returns the main application frame. Meant to be used from the contained components that do not
   * have direct access to the MainFrame.
   *
   * @return the main application frame
   */
  public MainFrame getMainFrame() {
    return mainFrame;
  }

  /**
   * Returns the combo box, where user enters the phone number to call to.
   *
   * @return the combo box, where user enters the phone number to call to.
   */
  public JComboBox getCallComboBox() {
    return phoneNumberCombo;
  }

  /**
   * Answers the given call.
   *
   * @param call the call to answer
   */
  public void answerCall(Call call) {
    new AnswerCallThread(call).start();
  }

  /**
   * Returns TRUE if this call is a call to an internal meta contact from the contact list,
   * otherwise returns FALSE.
   *
   * @return TRUE if this call is a call to an internal meta contact from the contact list,
   *     otherwise returns FALSE
   */
  public boolean isCallMetaContact() {
    return isCallMetaContact;
  }

  /**
   * Sets the isCallMetaContact variable to TRUE or FALSE. This defines if this call is a call to a
   * given meta contact selected from the contact list or a call to an external contact or phone
   * number.
   *
   * @param isCallMetaContact TRUE to define this call as a call to an internal meta contact and
   *     FALSE to define it as a call to an external contact or phone number.
   */
  public void setCallMetaContact(boolean isCallMetaContact) {
    this.isCallMetaContact = isCallMetaContact;
  }

  /**
   * Creates a call to the contact represented by the given string.
   *
   * @param contact the contact to call to
   */
  public void createCall(String contact) {
    CallPanel callPanel = new CallPanel(this, contact);

    mainFrame.addCallPanel(callPanel);

    new CreateCallThread(contact, callPanel).start();
  }

  /**
   * Creates a call to the given list of contacts.
   *
   * @param contacts the list of contacts to call to
   */
  public void createCall(Vector contacts) {
    CallPanel callPanel = new CallPanel(this, contacts);

    mainFrame.addCallPanel(callPanel);

    new CreateCallThread(contacts, callPanel).start();
  }

  /** Creates a call from a given Contact or a given String. */
  private class CreateCallThread extends Thread {
    Vector contacts;

    CallPanel callPanel;

    String stringContact;

    OperationSetBasicTelephony telephony;

    public CreateCallThread(String contact, CallPanel callPanel) {
      this.stringContact = contact;
      this.callPanel = callPanel;

      if (selectedCallProvider != null)
        telephony = mainFrame.getTelephonyOpSet(selectedCallProvider);
    }

    public CreateCallThread(Vector contacts, CallPanel callPanel) {
      this.contacts = contacts;
      this.callPanel = callPanel;

      if (selectedCallProvider != null)
        telephony = mainFrame.getTelephonyOpSet(selectedCallProvider);
    }

    public void run() {
      if (telephony == null) return;

      Call createdCall = null;

      if (contacts != null) {
        Contact contact = (Contact) contacts.get(0);

        // NOTE: The multi user call is not yet implemented!
        // We just get the first contact and create a call for him.
        try {
          createdCall = telephony.createCall(contact);
        } catch (OperationFailedException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(contact.getDisplayName()).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        }

        // If the call is successfully created we set the created
        // Call instance to the already existing CallPanel and we
        // add this call to the active calls.
        if (createdCall != null) {
          callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL);

          activeCalls.put(createdCall, callPanel);
        }
      } else {
        try {
          createdCall = telephony.createCall(stringContact);
        } catch (ParseException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(stringContact).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        } catch (OperationFailedException e) {
          logger.error("The call could not be created: " + e);

          callPanel.getParticipantPanel(stringContact).setState(e.getMessage());

          removeCallPanelWait(callPanel);
        }

        // If the call is successfully created we set the created
        // Call instance to the already existing CallPanel and we
        // add this call to the active calls.
        if (createdCall != null) {
          callPanel.setCall(createdCall, GuiCallParticipantRecord.OUTGOING_CALL);

          activeCalls.put(createdCall, callPanel);
        }
      }
    }
  }

  /** Answers all call participants in the given call. */
  private class AnswerCallThread extends Thread {
    private Call call;

    public AnswerCallThread(Call call) {
      this.call = call;
    }

    public void run() {
      ProtocolProviderService pps = call.getProtocolProvider();

      Iterator participants = call.getCallParticipants();

      while (participants.hasNext()) {
        CallParticipant participant = (CallParticipant) participants.next();

        OperationSetBasicTelephony telephony = mainFrame.getTelephonyOpSet(pps);

        try {
          telephony.answerCallParticipant(participant);
        } catch (OperationFailedException e) {
          logger.error(
              "Could not answer to : " + participant + " caused by the following exception: " + e);
        }
      }
    }
  }
}
@SuppressWarnings("serial")
public class HelpBrowserFrame extends JFrame {

  private static final Logger logger = Logger.getLogger(HelpBrowserFrame.class.getName());

  boolean plugin;
  HelpBrowser browser;

  private void Init() throws Exception {
    // ------------------------------------------------------------------------
    // Configure objects
    // ------------------------------------------------------------------------
    this.setTitle("Frost - Help Browser");
    this.setResizable(true);

    browser.setPreferredSize(new Dimension(780, 550));

    this.getContentPane().add(browser);
  }

  @Override
  protected void processWindowEvent(final WindowEvent e) {
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      if (!plugin) {
        dispose();
        System.exit(0);
      } else {
        saveWindowState();
        setVisible(false);
      }
    } else {
      super.processWindowEvent(e);
    }
  }

  /** Shorthand for ziphelp usage */
  public HelpBrowserFrame(final String langlocale, final String zipfile) {
    this(langlocale, "jar:file:" + zipfile + "!/", "index.html", true);
  }

  /** Complete for browser usage */
  public HelpBrowserFrame(
      final String langlocale, final String zipfile, final String startpage, boolean plugin) {
    this.plugin = plugin;

    this.browser = new HelpBrowser(this, langlocale, zipfile, startpage);

    setIconImage(MiscToolkit.loadImageIcon("/data/toolbar/help-browser.png").getImage());

    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
    try {
      Init();
      if (!plugin) {
        // standalone - fix size
        this.setSize(new Dimension(780, 550));
      } else {
        loadWindowState();
      }
    } catch (final Throwable e) {
      logger.log(Level.SEVERE, "Exception thrown in constructor", e);
    }
  }

  public void showHelpPage(final String page) {
    browser.setHelpPage(page);
  }

  public void showHelpPage_htmlLink(final String page) {
    browser.setHelpPage(page);
  }

  public void showHelpPage_alias(final String page) {
    browser.setHelpPage(page);
  }

  private void saveWindowState() {
    final Rectangle bounds = getBounds();
    boolean isMaximized = ((getExtendedState() & Frame.MAXIMIZED_BOTH) != 0);

    Core.frostSettings.setValue("helpBrowser.lastFrameMaximized", isMaximized);

    if (!isMaximized) { // Only save the current dimension if frame is not maximized
      Core.frostSettings.setValue("helpBrowser.lastFrameHeight", bounds.height);
      Core.frostSettings.setValue("helpBrowser.lastFrameWidth", bounds.width);
      Core.frostSettings.setValue("helpBrowser.lastFramePosX", bounds.x);
      Core.frostSettings.setValue("helpBrowser.lastFramePosY", bounds.y);
    }
  }

  private void loadWindowState() {
    // load size, location and state of window
    int lastHeight = Core.frostSettings.getIntValue("helpBrowser.lastFrameHeight");
    int lastWidth = Core.frostSettings.getIntValue("helpBrowser.lastFrameWidth");
    final int lastPosX = Core.frostSettings.getIntValue("helpBrowser.lastFramePosX");
    final int lastPosY = Core.frostSettings.getIntValue("helpBrowser.lastFramePosY");
    final boolean lastMaximized = Core.frostSettings.getBoolValue("helpBrowser.lastFrameMaximized");

    if (lastHeight <= 0 || lastWidth <= 0) {
      // first call
      setSize(780, 550);
      setLocationRelativeTo(MainFrame.getInstance());
      return;
    }

    final Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();

    if (lastWidth < 100) {
      lastWidth = 780;
    }
    if (lastHeight < 100) {
      lastHeight = 550;
    }

    if ((lastPosX + lastWidth) > scrSize.width) {
      setSize(780, 550);
      setLocationRelativeTo(MainFrame.getInstance());
      return;
    }

    if ((lastPosY + lastHeight) > scrSize.height) {
      setSize(780, 550);
      setLocationRelativeTo(MainFrame.getInstance());
      return;
    }

    setBounds(lastPosX, lastPosY, lastWidth, lastHeight);

    if (lastMaximized) {
      setExtendedState(getExtendedState() | Frame.MAXIMIZED_BOTH);
    }
  }
}
Exemple #19
0
/**
 * The dialog used as menu.
 *
 * @author Damian Minkov
 */
public class SelectAvatarMenu extends SIPCommPopupMenu implements ActionListener {
  /** Logger for this class. */
  private static final Logger logger = Logger.getLogger(SelectAvatarMenu.class);

  /** Name of choose button. */
  private static final String CHOSE_BUTTON_NAME = "chooseButton";

  /** Name of remove button. */
  private static final String REMOVE_BUTTON_NAME = "removeButton";

  /** Name of clear button. */
  private static final String CLEAR_BUTTON_NAME = "clearButton";

  /** Images shown as history. */
  private static final int MAX_STORED_IMAGES = 8;

  /** Ordered in columns. */
  private static final int IMAGES_PER_COLUMN = 4;

  /** Thumbnail width. */
  private static final int THUMB_WIDTH = 48;

  /** Thumbnail height. */
  private static final int THUMB_HEIGHT = 48;

  /** Buttons corresponding to images. */
  private SIPCommButton recentImagesButtons[] = new SIPCommButton[MAX_STORED_IMAGES];

  /** Next free image index number. */
  private int nextImageIndex = 0;

  /** The parent button using us. */
  private FramedImageWithMenu avatarImage;

  /**
   * Creates the dialog.
   *
   * @param avatarImage the button that will trigger this menu.
   */
  public SelectAvatarMenu(FramedImageWithMenu avatarImage) {
    this.avatarImage = avatarImage;

    init();

    this.pack();
  }

  /** Init visible components. */
  private void init() {
    TransparentPanel panel = new TransparentPanel(new BorderLayout());

    panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

    // Title label
    JLabel titleLabel =
        new JLabel(GuiActivator.getResources().getI18NString("service.gui.avatar.RECENT_ICONS"));
    titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));

    // fix for displaying text in menu
    // when using a dark OS theme (as default one in ubuntu)
    titleLabel.setForeground(new JMenuItem().getForeground());

    panel.add(titleLabel, BorderLayout.NORTH);

    // Init recent images grid
    TransparentPanel recentImagesGrid = new TransparentPanel();
    recentImagesGrid.setLayout(new GridLayout(0, IMAGES_PER_COLUMN));

    Dimension thumbsize = new Dimension(THUMB_WIDTH, THUMB_HEIGHT);
    for (int i = 0; i < MAX_STORED_IMAGES; i++) {
      this.recentImagesButtons[i] = new SIPCommButton(null);
      this.recentImagesButtons[i].setBorder(BorderFactory.createEtchedBorder());
      this.recentImagesButtons[i].setMaximumSize(thumbsize);
      this.recentImagesButtons[i].setMinimumSize(thumbsize);
      this.recentImagesButtons[i].setPreferredSize(thumbsize);
      this.recentImagesButtons[i].addActionListener(this);
      this.recentImagesButtons[i].setName("" + i);
      recentImagesGrid.add(this.recentImagesButtons[i]);
    }

    panel.add(recentImagesGrid, BorderLayout.CENTER);

    // Action buttons
    TransparentPanel buttonsPanel = new TransparentPanel();
    buttonsPanel.setLayout(new GridLayout(0, 1));

    // we use this menu item just to get its foreground color.
    Color linkColor = new JMenuItem().getForeground();

    addActionButton(
        buttonsPanel,
        this,
        GuiActivator.getResources().getI18NString("service.gui.avatar.CHOOSE_ICON"),
        CHOSE_BUTTON_NAME,
        linkColor);
    addActionButton(
        buttonsPanel,
        this,
        GuiActivator.getResources().getI18NString("service.gui.avatar.REMOVE_ICON"),
        REMOVE_BUTTON_NAME,
        linkColor);
    addActionButton(
        buttonsPanel,
        this,
        GuiActivator.getResources().getI18NString("service.gui.avatar.CLEAR_RECENT"),
        CLEAR_BUTTON_NAME,
        linkColor);

    panel.add(buttonsPanel, BorderLayout.SOUTH);

    this.setLayout(new BorderLayout());
    this.add(panel, BorderLayout.CENTER);
  }

  /**
   * Adds action buttons.
   *
   * @param buttonsPanel the panel to add to.
   * @param listener the listener for actions
   * @param text the text on the button.
   * @param name name of the button.
   * @param linkColor the color of the link.
   */
  private static void addActionButton(
      TransparentPanel buttonsPanel,
      ActionListener listener,
      String text,
      String name,
      Color linkColor) {
    SIPCommLinkButton button = new SIPCommLinkButton(text);
    button.setName(name);
    button.addActionListener(listener);
    button.setOpaque(false);
    button.setLinkColor(linkColor);

    TransparentPanel panel = new TransparentPanel(new BorderLayout());
    panel.add(button, BorderLayout.WEST);
    buttonsPanel.add(panel);
  }

  @Override
  public void setVisible(boolean b) {
    refreshRecentImages();
    super.setVisible(b);
  }

  /** Refresh images with those stored locally. */
  public void refreshRecentImages() {
    int i;

    for (i = 0; i < MAX_STORED_IMAGES; i++) {
      BufferedImage image = AvatarStackManager.loadImage(i);
      if (image == null) break;

      this.recentImagesButtons[i].setImage(createThumbnail(image));
      this.recentImagesButtons[i].setEnabled(true);
    }

    if (i < MAX_STORED_IMAGES) {
      this.nextImageIndex = i;

      for (; i < MAX_STORED_IMAGES; i++) {
        this.recentImagesButtons[i].setImage(null);
        this.recentImagesButtons[i].setEnabled(false);
      }
    } else this.nextImageIndex = MAX_STORED_IMAGES;
  }

  /**
   * Create thumbnail for the image.
   *
   * @param image to scale.
   * @return the thumbnail image.
   */
  private static BufferedImage createThumbnail(BufferedImage image) {
    int width = image.getWidth();
    int height = image.getHeight();

    // Image smaller than the thumbnail size
    if (width < THUMB_WIDTH && height < THUMB_HEIGHT) return image;

    Image i;

    if (width > height) i = image.getScaledInstance(THUMB_WIDTH, -1, Image.SCALE_SMOOTH);
    else i = image.getScaledInstance(-1, THUMB_HEIGHT, Image.SCALE_SMOOTH);

    return ImageUtils.getBufferedImage(i);
  }

  /**
   * Here is all the action. Stores the selected image into protocols and if needed update it ina
   * AccountStatusPanel.
   *
   * @param image the new image.
   */
  private void setNewImage(final BufferedImage image) {
    // Use separate thread to be sure we don't block UI thread.
    new Thread() {
      public void run() {
        AccountManager accountManager = GuiActivator.getAccountManager();

        for (AccountID accountID : accountManager.getStoredAccounts()) {
          if (accountManager.isAccountLoaded(accountID)) {
            ProtocolProviderService protocolProvider =
                GuiActivator.getRegisteredProviderForAccount(accountID);

            if (protocolProvider != null && protocolProvider.isRegistered()) {
              OperationSetAvatar opSetAvatar =
                  protocolProvider.getOperationSet(OperationSetAvatar.class);

              if (opSetAvatar != null) {
                byte[] imageByte = null;
                // Sets new avatar if not null. Otherwise, the
                // opSetAvatar.setAvatar(null) will removes the
                // current one.
                if (image != null) {
                  imageByte = ImageUtils.toByteArray(image);
                }
                try {
                  opSetAvatar.setAvatar(imageByte);
                } catch (Throwable t) {
                  logger.error("Error setting image", t);
                }
              }
            }
          }
        }
      }
    }.start();
  }

  /** Clear stored images. */
  private void clearRecentImages() {
    for (int i = 0; i < MAX_STORED_IMAGES; i++) {
      this.recentImagesButtons[i].setImage(null);
      this.recentImagesButtons[i].setEnabled(false);
      AvatarStackManager.deleteImage(i);
    }

    this.nextImageIndex = 0;
  }

  /**
   * Action performed on various action links(buttons).
   *
   * @param e the action.
   */
  public void actionPerformed(ActionEvent e) {
    JButton src = (JButton) e.getSource();

    if (src instanceof SIPCommButton) {
      // Load image
      int index = Integer.parseInt(src.getName());
      BufferedImage image = AvatarStackManager.loadImage(index);

      // Set the new image
      setNewImage(image);
    } else if (src.getName().equals("chooseButton")) {
      // Open the image picker
      Image currentImage = this.avatarImage.getAvatar();

      ImagePickerDialog dialog = new ImagePickerDialog(96, 96);

      byte[] bimage = dialog.showDialog(currentImage);

      if (bimage == null) return;

      // New image
      BufferedImage image = ImageUtils.getBufferedImage(new ImageIcon(bimage).getImage());

      // Store image
      if (this.nextImageIndex == MAX_STORED_IMAGES) {
        // No more place to store images
        // Pop the first element (index 0)
        AvatarStackManager.popFirstImage(MAX_STORED_IMAGES);

        this.nextImageIndex = MAX_STORED_IMAGES - 1;
      }

      // Store the new image on hard drive
      AvatarStackManager.storeImage(image, this.nextImageIndex);

      // Inform protocols about the new image
      setNewImage(image);
    } else if (src.getName().equals("removeButton")) {
      // Removes the current photo.
      setNewImage(null);
    } else if (src.getName().equals("clearButton")) {
      clearRecentImages();
    }

    setVisible(false);
  }
}
Exemple #20
0
/**
 * The <tt>StatusSubMenu</tt> provides a menu which allow to select the status for each of the
 * protocol providers registered when the menu appears
 *
 * @author Nicolas Chamouard
 */
public class StatusSubMenu extends JMenu {
  /** A reference of <tt>Systray</tt> */
  private SystrayServiceJdicImpl parentSystray;

  /** Contains all accounts and corresponding menus. */
  private Hashtable accountSelectors = new Hashtable();

  private Logger logger = Logger.getLogger(StatusSubMenu.class);

  /**
   * Creates an instance of <tt>StatusSubMenu</tt>.
   *
   * @param tray a reference of the parent <tt>Systray</tt>
   */
  public StatusSubMenu(SystrayServiceJdicImpl tray) {

    parentSystray = tray;

    this.setText(Resources.getString("setStatus"));
    this.setIcon(Resources.getImage("statusMenuIcon"));

    /* makes the menu look better */
    this.setPreferredSize(new java.awt.Dimension(28, 24));

    this.init();
  }

  /**
   * Adds the account corresponding to the given protocol provider to this menu.
   *
   * @param protocolProvider the protocol provider corresponding to the account to add
   */
  private void addAccount(ProtocolProviderService protocolProvider) {
    OperationSetPresence presence =
        (OperationSetPresence) protocolProvider.getOperationSet(OperationSetPresence.class);

    if (presence == null) {
      StatusSimpleSelector simpleSelector =
          new StatusSimpleSelector(parentSystray, protocolProvider);

      this.accountSelectors.put(protocolProvider.getAccountID(), simpleSelector);
      this.add(simpleSelector);
    } else {
      StatusSelector statusSelector = new StatusSelector(parentSystray, protocolProvider, presence);

      this.accountSelectors.put(protocolProvider.getAccountID(), statusSelector);
      this.add(statusSelector);

      presence.addProviderPresenceStatusListener(new SystrayProviderPresenceStatusListener());
    }
  }

  /**
   * Removes the account corresponding to the given protocol provider from this menu.
   *
   * @param protocolProvider the protocol provider corresponding to the account to remove.
   */
  private void removeAccount(ProtocolProviderService protocolProvider) {
    Component c = (Component) this.accountSelectors.get(protocolProvider.getAccountID());

    this.remove(c);
  }

  /**
   * We fill the protocolProviderTable with all running protocol providers at the start of the
   * bundle.
   */
  private void init() {
    SystrayActivator.bundleContext.addServiceListener(new ProtocolProviderServiceListener());

    ServiceReference[] protocolProviderRefs = null;
    try {
      protocolProviderRefs =
          SystrayActivator.bundleContext.getServiceReferences(
              ProtocolProviderService.class.getName(), null);
    } catch (InvalidSyntaxException ex) {
      // this shouldn't happen since we're providing no parameter string
      // but let's log just in case.
      logger.error("Error while retrieving service refs", ex);
      return;
    }

    // in case we found any
    if (protocolProviderRefs != null) {

      for (int i = 0; i < protocolProviderRefs.length; i++) {
        ProtocolProviderService provider =
            (ProtocolProviderService)
                SystrayActivator.bundleContext.getService(protocolProviderRefs[i]);

        boolean isHidden =
            provider.getAccountID().getAccountProperties().get("HIDDEN_PROTOCOL") != null;

        if (!isHidden) this.addAccount(provider);
      }
    }
  }

  /**
   * Listens for <tt>ServiceEvent</tt>s indicating that a <tt>ProtocolProviderService</tt> has been
   * registered and completes the account status menu.
   */
  private class ProtocolProviderServiceListener implements ServiceListener {
    /**
     * When a service is registered or unregistered, we update the provider tables and add/remove
     * listeners (if it supports BasicInstantMessenging implementation)
     *
     * @param event ServiceEvent
     */
    public void serviceChanged(ServiceEvent event) {
      // if the event is caused by a bundle being stopped, we don't want to
      // know
      if (event.getServiceReference().getBundle().getState() == Bundle.STOPPING) {
        return;
      }

      Object service = SystrayActivator.bundleContext.getService(event.getServiceReference());

      if (!(service instanceof ProtocolProviderService)) return;

      ProtocolProviderService provider = (ProtocolProviderService) service;

      if (event.getType() == ServiceEvent.REGISTERED) addAccount(provider);

      if (event.getType() == ServiceEvent.UNREGISTERING) removeAccount(provider);
    }
  }

  /**
   * Listens for all providerStatusChanged and providerStatusMessageChanged events in order to
   * refresh the account status panel, when a status is changed.
   */
  private class SystrayProviderPresenceStatusListener implements ProviderPresenceStatusListener {
    /** Fired when an account has changed its status. We update the icon in the menu. */
    public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) {
      ProtocolProviderService pps = evt.getProvider();

      StatusSelector selectorBox = (StatusSelector) accountSelectors.get(pps.getAccountID());

      if (selectorBox == null) return;

      selectorBox.updateStatus(evt.getNewStatus());
    }

    public void providerStatusMessageChanged(PropertyChangeEvent evt) {}
  }
}
 /** warns events */
 private void warn(String msg, Throwable t) {
   Logger.getLogger(getClass().getName()).log(Level.WARNING, msg, t);
 }
Exemple #22
0
/** Denotes a front-end controller for the client. */
public final class ClientController implements ActionProvider, CaptureCallback, AnalysisCallback {
  // INNER TYPES

  /** Provides a default tool context implementation. */
  static final class DefaultToolContext implements ToolContext {
    // VARIABLES

    private final int startSampleIdx;
    private final int endSampleIdx;

    // CONSTRUCTORS

    /**
     * Creates a new DefaultToolContext instance.
     *
     * @param aStartSampleIdx the starting sample index;
     * @param aEndSampleIdx the ending sample index.
     */
    public DefaultToolContext(final int aStartSampleIdx, final int aEndSampleIdx) {
      this.startSampleIdx = aStartSampleIdx;
      this.endSampleIdx = aEndSampleIdx;
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getEndSampleIndex() */
    @Override
    public int getEndSampleIndex() {
      return this.endSampleIdx;
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getLength() */
    @Override
    public int getLength() {
      return Math.max(0, this.endSampleIdx - this.startSampleIdx);
    }

    /** @see nl.lxtreme.ols.api.tools.ToolContext#getStartSampleIndex() */
    @Override
    public int getStartSampleIndex() {
      return this.startSampleIdx;
    }
  }

  // CONSTANTS

  private static final Logger LOG = Logger.getLogger(ClientController.class.getName());

  // VARIABLES

  private final ActionManager actionManager;
  private final BundleContext bundleContext;
  private final DataContainer dataContainer;
  private final EventListenerList evenListeners;
  private final ProjectManager projectManager;
  private final Host host;

  private MainFrame mainFrame;

  private volatile DeviceController currentDevCtrl;

  // CONSTRUCTORS

  /** Creates a new ClientController instance. */
  public ClientController(
      final BundleContext aBundleContext, final Host aHost, final ProjectManager aProjectManager) {
    this.bundleContext = aBundleContext;
    this.host = aHost;
    this.projectManager = aProjectManager;

    this.dataContainer = new DataContainer(this.projectManager);
    this.actionManager = new ActionManager();
    this.evenListeners = new EventListenerList();

    fillActionManager(this.actionManager);
  }

  // METHODS

  /**
   * Adds a cursor change listener.
   *
   * @param aListener the listener to add, cannot be <code>null</code>.
   */
  public void addCursorChangeListener(final DiagramCursorChangeListener aListener) {
    this.evenListeners.add(DiagramCursorChangeListener.class, aListener);
  }

  /**
   * Adds the given device controller to this controller.
   *
   * @param aDeviceController the device controller to add, cannot be <code>null</code>.
   */
  public void addDevice(final DeviceController aDeviceController) {
    if (this.mainFrame != null) {
      if (this.mainFrame.addDeviceMenuItem(aDeviceController)) {
        this.currentDevCtrl = aDeviceController;
      }
    }

    updateActions();
  }

  /**
   * Adds the given exporter to this controller.
   *
   * @param aExporter the exporter to add, cannot be <code>null</code>.
   */
  public void addExporter(final Exporter aExporter) {
    if (this.mainFrame != null) {
      this.mainFrame.addExportMenuItem(aExporter.getName());
    }

    updateActions();
  }

  /**
   * Adds the given tool to this controller.
   *
   * @param aTool the tool to add, cannot be <code>null</code>.
   */
  public void addTool(final Tool aTool) {
    if (this.mainFrame != null) {
      this.mainFrame.addToolMenuItem(aTool.getName());
    }

    updateActions();
  }

  /** @see nl.lxtreme.ols.api.tools.AnalysisCallback#analysisAborted(java.lang.String) */
  @Override
  public void analysisAborted(final String aReason) {
    setStatus("Analysis aborted! " + aReason);

    updateActions();
  }

  /**
   * @see
   *     nl.lxtreme.ols.api.tools.AnalysisCallback#analysisComplete(nl.lxtreme.ols.api.data.CapturedData)
   */
  @Override
  public void analysisComplete(final CapturedData aNewCapturedData) {
    if (aNewCapturedData != null) {
      this.dataContainer.setCapturedData(aNewCapturedData);
    }
    if (this.mainFrame != null) {
      repaintMainFrame();
    }

    setStatus("");
    updateActions();
  }

  /** Cancels the current capturing (if in progress). */
  public void cancelCapture() {
    final DeviceController deviceController = getDeviceController();
    if (deviceController == null) {
      return;
    }

    deviceController.cancel();
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#captureAborted(java.lang.String) */
  @Override
  public void captureAborted(final String aReason) {
    setStatus("Capture aborted! " + aReason);
    updateActions();
  }

  /**
   * @see
   *     nl.lxtreme.ols.api.devices.CaptureCallback#captureComplete(nl.lxtreme.ols.api.data.CapturedData)
   */
  @Override
  public void captureComplete(final CapturedData aCapturedData) {
    setCapturedData(aCapturedData);

    setStatus("Capture finished at {0,date,medium} {0,time,medium}.", new Date());

    updateActions();
  }

  /**
   * Captures the data of the current device controller.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   * @return <code>true</code> if the capture succeeded, <code>false</code> otherwise.
   * @throws IOException in case of I/O problems.
   */
  public boolean captureData(final Window aParent) {
    final DeviceController devCtrl = getDeviceController();
    if (devCtrl == null) {
      return false;
    }

    try {
      if (devCtrl.setupCapture(aParent)) {
        setStatus(
            "Capture from {0} started at {1,date,medium} {1,time,medium} ...",
            devCtrl.getName(), new Date());

        devCtrl.captureData(this);
        return true;
      }

      return false;
    } catch (IOException exception) {
      captureAborted("I/O problem: " + exception.getMessage());

      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        exception.printStackTrace();
      }

      return false;
    } finally {
      updateActions();
    }
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#captureStarted(int, int, int) */
  @Override
  public synchronized void captureStarted(
      final int aSampleRate, final int aChannelCount, final int aChannelMask) {
    final Runnable runner =
        new Runnable() {
          @Override
          public void run() {
            updateActions();
          }
        };

    if (SwingUtilities.isEventDispatchThread()) {
      runner.run();
    } else {
      SwingUtilities.invokeLater(runner);
    }
  }

  /** Clears all current cursors. */
  public void clearAllCursors() {
    for (int i = 0; i < CapturedData.MAX_CURSORS; i++) {
      this.dataContainer.setCursorPosition(i, null);
    }
    fireCursorChangedEvent(0, -1); // removed...

    updateActions();
  }

  /** Clears the current device controller. */
  public void clearDeviceController() {
    this.currentDevCtrl = null;
  }

  /**
   * Clears the current project, and start over as it were a new project, in which no captured data
   * is shown.
   */
  public void createNewProject() {
    this.projectManager.createNewProject();

    if (this.mainFrame != null) {
      this.mainFrame.repaint();
    }

    updateActions();
  }

  /** Exits the client application. */
  public void exit() {
    if (this.host != null) {
      this.host.exit();
    }
  }

  /**
   * Exports the current diagram to the given exporter.
   *
   * @param aExporter the exporter to export to, cannot be <code>null</code>.
   * @param aOutputStream the output stream to write the export to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void exportTo(final Exporter aExporter, final OutputStream aOutputStream)
      throws IOException {
    if (this.mainFrame != null) {
      aExporter.export(this.dataContainer, this.mainFrame.getDiagramScrollPane(), aOutputStream);
    }
  }

  /** @see nl.lxtreme.ols.client.ActionProvider#getAction(java.lang.String) */
  public Action getAction(final String aID) {
    return this.actionManager.getAction(aID);
  }

  /** @return the dataContainer */
  public DataContainer getDataContainer() {
    return this.dataContainer;
  }

  /**
   * Returns the current device controller.
   *
   * @return the current device controller, can be <code>null</code>.
   */
  public DeviceController getDeviceController() {
    return this.currentDevCtrl;
  }

  /**
   * Returns all current tools known to the OSGi framework.
   *
   * @return a collection of tools, never <code>null</code>.
   */
  public final Collection<DeviceController> getDevices() {
    final List<DeviceController> tools = new ArrayList<DeviceController>();
    synchronized (this.bundleContext) {
      try {
        final ServiceReference[] serviceRefs =
            this.bundleContext.getAllServiceReferences(DeviceController.class.getName(), null);
        for (ServiceReference serviceRef : serviceRefs) {
          tools.add((DeviceController) this.bundleContext.getService(serviceRef));
        }
      } catch (InvalidSyntaxException exception) {
        throw new RuntimeException(exception);
      }
    }
    return tools;
  }

  /**
   * Returns the exporter with the given name.
   *
   * @param aName the name of the exporter to return, cannot be <code>null</code>.
   * @return the exporter with the given name, can be <code>null</code> if no such exporter is
   *     found.
   * @throws IllegalArgumentException in case the given name was <code>null</code> or empty.
   */
  public Exporter getExporter(final String aName) throws IllegalArgumentException {
    if ((aName == null) || aName.trim().isEmpty()) {
      throw new IllegalArgumentException("Name cannot be null or empty!");
    }

    try {
      final ServiceReference[] serviceRefs =
          this.bundleContext.getAllServiceReferences(Exporter.class.getName(), null);
      final int count = (serviceRefs == null) ? 0 : serviceRefs.length;

      for (int i = 0; i < count; i++) {
        final Exporter exporter = (Exporter) this.bundleContext.getService(serviceRefs[i]);

        if (aName.equals(exporter.getName())) {
          return exporter;
        }
      }

      return null;
    } catch (InvalidSyntaxException exception) {
      throw new RuntimeException("getExporter failed!", exception);
    }
  }

  /**
   * Returns the names of all current available exporters.
   *
   * @return an array of exporter names, never <code>null</code>, but can be empty.
   */
  public String[] getExporterNames() {
    try {
      final ServiceReference[] serviceRefs =
          this.bundleContext.getAllServiceReferences(Exporter.class.getName(), null);
      final int count = serviceRefs == null ? 0 : serviceRefs.length;

      final String[] result = new String[count];

      for (int i = 0; i < count; i++) {
        final Exporter exporter = (Exporter) this.bundleContext.getService(serviceRefs[i]);

        result[i] = exporter.getName();
        this.bundleContext.ungetService(serviceRefs[i]);
      }

      return result;
    } catch (InvalidSyntaxException exception) {
      throw new RuntimeException("getAllExporterNames failed!", exception);
    }
  }

  /**
   * Returns the current project's filename.
   *
   * @return a project filename, as file object, can be <code>null</code>.
   */
  public File getProjectFilename() {
    return this.projectManager.getCurrentProject().getFilename();
  }

  /**
   * Returns all current tools known to the OSGi framework.
   *
   * @return a collection of tools, never <code>null</code>.
   */
  public final Collection<Tool> getTools() {
    final List<Tool> tools = new ArrayList<Tool>();
    synchronized (this.bundleContext) {
      try {
        final ServiceReference[] serviceRefs =
            this.bundleContext.getAllServiceReferences(Tool.class.getName(), null);
        for (ServiceReference serviceRef : serviceRefs) {
          tools.add((Tool) this.bundleContext.getService(serviceRef));
        }
      } catch (InvalidSyntaxException exception) {
        throw new RuntimeException(exception);
      }
    }
    return tools;
  }

  /**
   * Goes to the current cursor position of the cursor with the given index.
   *
   * @param aCursorIdx the index of the cursor to go to, >= 0 && < 10.
   */
  public void gotoCursorPosition(final int aCursorIdx) {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      final Long cursorPosition = this.dataContainer.getCursorPosition(aCursorIdx);
      if (cursorPosition != null) {
        this.mainFrame.gotoPosition(cursorPosition.longValue());
      }
    }
  }

  /** Goes to the current cursor position of the first available cursor. */
  public void gotoFirstAvailableCursor() {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
        if (this.dataContainer.isCursorPositionSet(c)) {
          final Long cursorPosition = this.dataContainer.getCursorPosition(c);
          if (cursorPosition != null) {
            this.mainFrame.gotoPosition(cursorPosition.longValue());
          }
          break;
        }
      }
    }
  }

  /** Goes to the current cursor position of the last available cursor. */
  public void gotoLastAvailableCursor() {
    if ((this.mainFrame != null) && this.dataContainer.isCursorsEnabled()) {
      for (int c = CapturedData.MAX_CURSORS - 1; c >= 0; c--) {
        if (this.dataContainer.isCursorPositionSet(c)) {
          final Long cursorPosition = this.dataContainer.getCursorPosition(c);
          if (cursorPosition != null) {
            this.mainFrame.gotoPosition(cursorPosition.longValue());
          }
          break;
        }
      }
    }
  }

  /** Goes to the position of the trigger. */
  public void gotoTriggerPosition() {
    if ((this.mainFrame != null) && this.dataContainer.hasTriggerData()) {
      final long position = this.dataContainer.getTriggerPosition();
      this.mainFrame.gotoPosition(position);
    }
  }

  /**
   * Returns whether there is a device selected or not.
   *
   * @return <code>true</code> if there is a device selected, <code>false</code> if no device is
   *     selected.
   */
  public synchronized boolean isDeviceSelected() {
    return this.currentDevCtrl != null;
  }

  /**
   * Returns whether the current device is setup at least once.
   *
   * @return <code>true</code> if the current device is setup, <code>false</code> otherwise.
   * @see #isDeviceSelected()
   */
  public synchronized boolean isDeviceSetup() {
    return (this.currentDevCtrl != null) && this.currentDevCtrl.isSetup();
  }

  /**
   * Returns whether or not the current project is changed.
   *
   * @return <code>true</code> if the current project is changed, <code>false</code> if the current
   *     project is not changed.
   */
  public boolean isProjectChanged() {
    return this.projectManager.getCurrentProject().isChanged();
  }

  /**
   * Loads an OLS data file from the given file.
   *
   * @param aFile the file to load as OLS data, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void openDataFile(final File aFile) throws IOException {
    final FileReader reader = new FileReader(aFile);

    try {
      final Project tempProject = this.projectManager.createTemporaryProject();
      OlsDataHelper.read(tempProject, reader);

      setChannelLabels(tempProject.getChannelLabels());
      setCapturedData(tempProject.getCapturedData());
      setCursorData(tempProject.getCursorPositions(), tempProject.isCursorsEnabled());
    } finally {
      reader.close();

      zoomToFit();

      updateActions();
    }
  }

  /**
   * Opens the project denoted by the given file.
   *
   * @param aFile the project file to open, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void openProjectFile(final File aFile) throws IOException {
    FileInputStream fis = new FileInputStream(aFile);

    this.projectManager.loadProject(fis);

    final Project project = this.projectManager.getCurrentProject();
    project.setFilename(aFile);

    zoomToFit();
  }

  /**
   * Removes the cursor denoted by the given cursor index.
   *
   * @param aCursorIdx the index of the cursor to remove, >= 0 && < 10.
   */
  public void removeCursor(final int aCursorIdx) {
    if (this.mainFrame != null) {
      this.dataContainer.setCursorPosition(aCursorIdx, null);
      fireCursorChangedEvent(aCursorIdx, -1); // removed...
    }

    updateActions();
  }

  /**
   * Removes a cursor change listener.
   *
   * @param aListener the listener to remove, cannot be <code>null</code>.
   */
  public void removeCursorChangeListener(final DiagramCursorChangeListener aListener) {
    this.evenListeners.remove(DiagramCursorChangeListener.class, aListener);
  }

  /**
   * Removes the given device from the list of devices.
   *
   * @param aDeviceController the device to remove, cannot be <code>null</code>.
   */
  public void removeDevice(final DeviceController aDeviceController) {
    if (this.currentDevCtrl == aDeviceController) {
      this.currentDevCtrl = null;
    }

    if (this.mainFrame != null) {
      this.mainFrame.removeDeviceMenuItem(aDeviceController.getName());
    }

    updateActions();
  }

  /**
   * Removes the given exporter from the list of exporters.
   *
   * @param aExporter the exporter to remove, cannot be <code>null</code>.
   */
  public void removeExporter(final Exporter aExporter) {
    if (this.mainFrame != null) {
      this.mainFrame.removeExportMenuItem(aExporter.getName());
    }

    updateActions();
  }

  /**
   * Removes the given tool from the list of tools.
   *
   * @param aTool the tool to remove, cannot be <code>null</code>.
   */
  public void removeTool(final Tool aTool) {
    if (this.mainFrame != null) {
      this.mainFrame.removeToolMenuItem(aTool.getName());
    }

    updateActions();
  }

  /**
   * Repeats the capture with the current settings.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public boolean repeatCaptureData(final Window aParent) {
    final DeviceController devCtrl = getDeviceController();
    if (devCtrl == null) {
      return false;
    }

    try {
      setStatus(
          "Capture from {0} started at {1,date,medium} {1,time,medium} ...",
          devCtrl.getName(), new Date());

      devCtrl.captureData(this);

      return true;
    } catch (IOException exception) {
      captureAborted("I/O problem: " + exception.getMessage());

      exception.printStackTrace();

      // Make sure to handle IO-interrupted exceptions properly!
      HostUtils.handleInterruptedException(exception);

      return false;
    } finally {
      updateActions();
    }
  }

  /**
   * Runs the tool denoted by the given name.
   *
   * @param aToolName the name of the tool to run, cannot be <code>null</code>;
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void runTool(final String aToolName, final Window aParent) {
    if (LOG.isLoggable(Level.INFO)) {
      LOG.log(Level.INFO, "Running tool: \"{0}\" ...", aToolName);
    }

    final Tool tool = findToolByName(aToolName);
    if (tool == null) {
      JOptionPane.showMessageDialog(
          aParent, "No such tool found: " + aToolName, "Error ...", JOptionPane.ERROR_MESSAGE);
    } else {
      final ToolContext context = createToolContext();
      tool.process(aParent, this.dataContainer, context, this);
    }

    updateActions();
  }

  /** @see nl.lxtreme.ols.api.devices.CaptureCallback#samplesCaptured(java.util.List) */
  @Override
  public void samplesCaptured(final List<Sample> aSamples) {
    if (this.mainFrame != null) {
      this.mainFrame.sampleCaptured(aSamples);
    }
    updateActions();
  }

  /**
   * Saves an OLS data file to the given file.
   *
   * @param aFile the file to save the OLS data to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void saveDataFile(final File aFile) throws IOException {
    final FileWriter writer = new FileWriter(aFile);
    try {
      final Project tempProject = this.projectManager.createTemporaryProject();
      tempProject.setCapturedData(this.dataContainer);

      OlsDataHelper.write(tempProject, writer);
    } finally {
      writer.flush();
      writer.close();
    }
  }

  /**
   * Saves the current project to the given file.
   *
   * @param aFile the file to save the project information to, cannot be <code>null</code>.
   * @throws IOException in case of I/O problems.
   */
  public void saveProjectFile(final String aName, final File aFile) throws IOException {
    FileOutputStream out = null;
    try {
      final Project project = this.projectManager.getCurrentProject();
      project.setFilename(aFile);
      project.setName(aName);

      out = new FileOutputStream(aFile);
      this.projectManager.saveProject(out);
    } finally {
      HostUtils.closeResource(out);
    }
  }

  /**
   * Sets whether or not cursors are enabled.
   *
   * @param aState <code>true</code> if the cursors should be enabled, <code>false</code> otherwise.
   */
  public void setCursorMode(final boolean aState) {
    this.dataContainer.setCursorEnabled(aState);
    // Reflect the change directly on the diagram...
    repaintMainFrame();

    updateActions();
  }

  /**
   * Sets the cursor position of the cursor with the given index.
   *
   * @param aCursorIdx the index of the cursor to set, >= 0 && < 10;
   * @param aLocation the mouse location on screen where the cursor should become, cannot be <code>
   *     null</code>.
   */
  public void setCursorPosition(final int aCursorIdx, final Point aLocation) {
    // Implicitly enable cursor mode, the user already had made its
    // intensions clear that he want to have this by opening up the
    // context menu anyway...
    setCursorMode(true);

    if (this.mainFrame != null) {
      // Convert the mouse-position to a sample index...
      final long sampleIdx = this.mainFrame.convertMousePositionToSampleIndex(aLocation);

      this.dataContainer.setCursorPosition(aCursorIdx, Long.valueOf(sampleIdx));

      fireCursorChangedEvent(aCursorIdx, aLocation.x);
    }

    updateActions();
  }

  /**
   * Sets the current device controller to the given value.
   *
   * @param aDeviceName the name of the device controller to set, cannot be <code>null</code>.
   */
  public synchronized void setDeviceController(final String aDeviceName) {
    if (LOG.isLoggable(Level.INFO)) {
      final String name = (aDeviceName == null) ? "no device" : aDeviceName;
      LOG.log(Level.INFO, "Setting current device controller to: \"{0}\" ...", name);
    }

    final Collection<DeviceController> devices = getDevices();
    for (DeviceController device : devices) {
      if (aDeviceName.equals(device.getName())) {
        this.currentDevCtrl = device;
      }
    }

    updateActions();
  }

  /** @param aMainFrame the mainFrame to set */
  public void setMainFrame(final MainFrame aMainFrame) {
    if (this.mainFrame != null) {
      this.projectManager.removePropertyChangeListener(this.mainFrame);
    }
    if (aMainFrame != null) {
      this.projectManager.addPropertyChangeListener(aMainFrame);
    }

    this.mainFrame = aMainFrame;
  }

  /**
   * Sets a status message.
   *
   * @param aMessage the message to set;
   * @param aMessageArgs the (optional) message arguments.
   */
  public final void setStatus(final String aMessage, final Object... aMessageArgs) {
    if (this.mainFrame != null) {
      this.mainFrame.setStatus(aMessage, aMessageArgs);
    }
  }

  /** Shows the "about OLS" dialog on screen. the parent window to use, can be <code>null</code>. */
  public void showAboutBox() {
    MainFrame.showAboutBox(this.host.getVersion());
  }

  /**
   * Shows a dialog with all running OSGi bundles.
   *
   * @param aOwner the owning window to use, can be <code>null</code>.
   */
  public void showBundlesDialog(final Window aOwner) {
    BundlesDialog dialog = new BundlesDialog(aOwner, this.bundleContext);
    if (dialog.showDialog()) {
      dialog.dispose();
      dialog = null;
    }
  }

  /**
   * Shows the label-editor dialog on screen.
   *
   * <p>Display the diagram labels dialog. Will block until the dialog is closed again.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void showLabelsDialog(final Window aParent) {
    if (this.mainFrame != null) {
      DiagramLabelsDialog dialog =
          new DiagramLabelsDialog(aParent, this.dataContainer.getChannelLabels());
      if (dialog.showDialog()) {
        final String[] channelLabels = dialog.getChannelLabels();
        setChannelLabels(channelLabels);
      }

      dialog.dispose();
      dialog = null;
    }
  }

  /**
   * Shows the settings-editor dialog on screen.
   *
   * <p>Display the diagram settings dialog. Will block until the dialog is closed again.
   *
   * @param aParent the parent window to use, can be <code>null</code>.
   */
  public void showModeSettingsDialog(final Window aParent) {
    if (this.mainFrame != null) {
      ModeSettingsDialog dialog = new ModeSettingsDialog(aParent, getDiagramSettings());
      if (dialog.showDialog()) {
        updateDiagramSettings(dialog.getDiagramSettings());
      }

      dialog.dispose();
      dialog = null;
    }
  }

  /** @param aOwner */
  public void showPreferencesDialog(final Window aParent) {
    GeneralSettingsDialog dialog = new GeneralSettingsDialog(aParent, getDiagramSettings());
    if (dialog.showDialog()) {
      updateDiagramSettings(dialog.getDiagramSettings());
    }

    dialog.dispose();
    dialog = null;
  }

  /** @see nl.lxtreme.ols.api.ProgressCallback#updateProgress(int) */
  @Override
  public void updateProgress(final int aPercentage) {
    if (this.mainFrame != null) {
      this.mainFrame.setProgress(aPercentage);
    }
  }

  /** Zooms in to the maximum zoom level. */
  public void zoomDefault() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomDefault();
    }

    updateActions();
  }

  /** Zooms in with a factor of 2.0. */
  public void zoomIn() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomIn();
    }

    updateActions();
  }

  /** Zooms out with a factor of 2.0. */
  public void zoomOut() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomOut();
    }

    updateActions();
  }

  /** Zooms to fit the diagram to the current window dimensions. */
  public void zoomToFit() {
    if (this.mainFrame != null) {
      this.mainFrame.zoomToFit();
    }

    updateActions();
  }

  /**
   * Returns the current main frame.
   *
   * @return the main frame, can be <code>null</code>.
   */
  final MainFrame getMainFrame() {
    return this.mainFrame;
  }

  /**
   * Creates the tool context denoting the range of samples that should be analysed by a tool.
   *
   * @return a tool context, never <code>null</code>.
   */
  private ToolContext createToolContext() {
    int startOfDecode = -1;
    int endOfDecode = -1;

    final int dataLength = this.dataContainer.getValues().length;
    if (this.dataContainer.isCursorsEnabled()) {
      if (this.dataContainer.isCursorPositionSet(0)) {
        final Long cursor1 = this.dataContainer.getCursorPosition(0);
        startOfDecode = this.dataContainer.getSampleIndex(cursor1.longValue()) - 1;
      }
      if (this.dataContainer.isCursorPositionSet(1)) {
        final Long cursor2 = this.dataContainer.getCursorPosition(1);
        endOfDecode = this.dataContainer.getSampleIndex(cursor2.longValue()) + 1;
      }
    } else {
      startOfDecode = 0;
      endOfDecode = dataLength;
    }

    startOfDecode = Math.max(0, startOfDecode);
    if ((endOfDecode < 0) || (endOfDecode >= dataLength)) {
      endOfDecode = dataLength - 1;
    }

    return new DefaultToolContext(startOfDecode, endOfDecode);
  }

  /** @param aActionManager */
  private void fillActionManager(final ActionManager aActionManager) {
    aActionManager.add(new NewProjectAction(this));
    aActionManager.add(new OpenProjectAction(this));
    aActionManager.add(new SaveProjectAction(this)).setEnabled(false);
    aActionManager.add(new SaveProjectAsAction(this)).setEnabled(false);
    aActionManager.add(new OpenDataFileAction(this));
    aActionManager.add(new SaveDataFileAction(this)).setEnabled(false);
    aActionManager.add(new ExitAction(this));

    aActionManager.add(new CaptureAction(this));
    aActionManager.add(new CancelCaptureAction(this)).setEnabled(false);
    aActionManager.add(new RepeatCaptureAction(this)).setEnabled(false);

    aActionManager.add(new ZoomInAction(this)).setEnabled(false);
    aActionManager.add(new ZoomOutAction(this)).setEnabled(false);
    aActionManager.add(new ZoomDefaultAction(this)).setEnabled(false);
    aActionManager.add(new ZoomFitAction(this)).setEnabled(false);

    aActionManager.add(new GotoTriggerAction(this)).setEnabled(false);
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      aActionManager.add(new GotoNthCursorAction(this, c)).setEnabled(false);
    }
    aActionManager.add(new GotoFirstCursorAction(this)).setEnabled(false);
    aActionManager.add(new GotoLastCursorAction(this)).setEnabled(false);
    aActionManager.add(new ClearCursors(this)).setEnabled(false);
    aActionManager.add(new SetCursorModeAction(this));
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      aActionManager.add(new SetCursorAction(this, c));
    }

    aActionManager.add(new ShowGeneralSettingsAction(this));
    aActionManager.add(new ShowModeSettingsAction(this));
    aActionManager.add(new ShowDiagramLabelsAction(this));

    aActionManager.add(new HelpAboutAction(this));
    aActionManager.add(new ShowBundlesAction(this));
  }

  /**
   * Searches for the tool with the given name.
   *
   * @param aToolName the name of the tool to search for, cannot be <code>null</code>.
   * @return the tool with the given name, can be <code>null</code> if no such tool can be found.
   */
  private Tool findToolByName(final String aToolName) {
    Tool tool = null;

    final Collection<Tool> tools = getTools();
    for (Tool _tool : tools) {
      if (aToolName.equals(_tool.getName())) {
        tool = _tool;
        break;
      }
    }
    return tool;
  }

  /**
   * @param aCursorIdx
   * @param aMouseXpos
   */
  private void fireCursorChangedEvent(final int aCursorIdx, final int aMouseXpos) {
    final DiagramCursorChangeListener[] listeners =
        this.evenListeners.getListeners(DiagramCursorChangeListener.class);
    for (final DiagramCursorChangeListener listener : listeners) {
      if (aMouseXpos >= 0) {
        listener.cursorChanged(aCursorIdx, aMouseXpos);
      } else {
        listener.cursorRemoved(aCursorIdx);
      }
    }
  }

  /**
   * Returns the current diagram settings.
   *
   * @return the current diagram settings, can be <code>null</code> if there is no main frame to
   *     take the settings from.
   */
  private DiagramSettings getDiagramSettings() {
    return this.mainFrame != null ? this.mainFrame.getDiagramSettings() : null;
  }

  /** Dispatches a request to repaint the entire main frame. */
  private void repaintMainFrame() {
    SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            ClientController.this.mainFrame.repaint();
          }
        });
  }

  /**
   * Sets the captured data and zooms the view to show all the data.
   *
   * @param aCapturedData the new captured data to set, cannot be <code>null</code>.
   */
  private void setCapturedData(final CapturedData aCapturedData) {
    this.dataContainer.setCapturedData(aCapturedData);

    if (this.mainFrame != null) {
      this.mainFrame.zoomToFit();
    }
  }

  /**
   * Set the channel labels.
   *
   * @param aChannelLabels the channel labels to set, cannot be <code>null</code>.
   */
  private void setChannelLabels(final String[] aChannelLabels) {
    if (aChannelLabels != null) {
      this.dataContainer.setChannelLabels(aChannelLabels);
      this.mainFrame.setChannelLabels(aChannelLabels);
    }
  }

  /**
   * @param aCursorData the cursor positions to set, cannot be <code>null</code>;
   * @param aCursorsEnabled <code>true</code> if cursors should be enabled, <code>false</code> if
   *     they should be disabled.
   */
  private void setCursorData(final Long[] aCursorData, final boolean aCursorsEnabled) {
    this.dataContainer.setCursorEnabled(aCursorsEnabled);
    for (int i = 0; i < CapturedData.MAX_CURSORS; i++) {
      this.dataContainer.setCursorPosition(i, aCursorData[i]);
    }
  }

  /** Synchronizes the state of the actions to the current state of this host. */
  private void updateActions() {
    final DeviceController currentDeviceController = getDeviceController();

    final boolean deviceControllerSet = currentDeviceController != null;
    final boolean deviceCapturing = deviceControllerSet && currentDeviceController.isCapturing();
    final boolean deviceSetup =
        deviceControllerSet && !deviceCapturing && currentDeviceController.isSetup();

    getAction(CaptureAction.ID).setEnabled(deviceControllerSet);
    getAction(CancelCaptureAction.ID).setEnabled(deviceCapturing);
    getAction(RepeatCaptureAction.ID).setEnabled(deviceSetup);

    final boolean projectChanged = this.projectManager.getCurrentProject().isChanged();
    final boolean projectSavedBefore =
        this.projectManager.getCurrentProject().getFilename() != null;
    final boolean dataAvailable = this.dataContainer.hasCapturedData();

    getAction(SaveProjectAction.ID).setEnabled(projectChanged);
    getAction(SaveProjectAsAction.ID).setEnabled(projectSavedBefore && projectChanged);
    getAction(SaveDataFileAction.ID).setEnabled(dataAvailable);

    getAction(ZoomInAction.ID).setEnabled(dataAvailable);
    getAction(ZoomOutAction.ID).setEnabled(dataAvailable);
    getAction(ZoomDefaultAction.ID).setEnabled(dataAvailable);
    getAction(ZoomFitAction.ID).setEnabled(dataAvailable);

    final boolean triggerEnable = dataAvailable && this.dataContainer.hasTriggerData();
    getAction(GotoTriggerAction.ID).setEnabled(triggerEnable);

    // Update the cursor actions accordingly...
    final boolean enableCursors = dataAvailable && this.dataContainer.isCursorsEnabled();

    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      final boolean enabled = enableCursors && this.dataContainer.isCursorPositionSet(c);
      getAction(GotoNthCursorAction.getID(c)).setEnabled(enabled);
    }

    getAction(GotoFirstCursorAction.ID).setEnabled(enableCursors);
    getAction(GotoLastCursorAction.ID).setEnabled(enableCursors);

    getAction(SetCursorModeAction.ID).setEnabled(dataAvailable);
    getAction(SetCursorModeAction.ID)
        .putValue(Action.SELECTED_KEY, Boolean.valueOf(this.dataContainer.isCursorsEnabled()));

    boolean anyCursorSet = false;
    for (int c = 0; c < CapturedData.MAX_CURSORS; c++) {
      final boolean cursorPositionSet = this.dataContainer.isCursorPositionSet(c);
      anyCursorSet |= cursorPositionSet;

      final Action action = getAction(SetCursorAction.getCursorId(c));
      action.setEnabled(dataAvailable);
      action.putValue(Action.SELECTED_KEY, Boolean.valueOf(cursorPositionSet));
    }

    getAction(ClearCursors.ID).setEnabled(enableCursors && anyCursorSet);
  }

  /**
   * Should be called after the diagram settings are changed. This method will cause the settings to
   * be set on the main frame and writes them to the preference store.
   *
   * @param aSettings the (new/changed) diagram settings to set, cannot be <code>null</code>.
   */
  private void updateDiagramSettings(final DiagramSettings aSettings) {
    if (this.mainFrame != null) {
      this.mainFrame.setDiagramSettings(aSettings);
      repaintMainFrame();
    }
  }
}
public class XBaseWindow implements XConstants, XUtilConstants {
  private static final Logger log = Logger.getLogger("sun.awt.X11.XBaseWindow");
  private static final Logger insLog = Logger.getLogger("sun.awt.X11.insets.XBaseWindow");
  private static final Logger eventLog = Logger.getLogger("sun.awt.X11.event.XBaseWindow");
  private static final Logger focusLog = Logger.getLogger("sun.awt.X11.focus.XBaseWindow");
  private static final Logger grabLog = Logger.getLogger("sun.awt.X11.grab.XBaseWindow");

  public static final String PARENT_WINDOW = "parent window", // parent window, Long
      BOUNDS = "bounds", // bounds of the window, Rectangle
      OVERRIDE_REDIRECT = "overrideRedirect", // override_redirect setting, Boolean
      EVENT_MASK = "event mask", // event mask, Integer
      VALUE_MASK = "value mask", // value mask, Long
      BORDER_PIXEL = "border pixel", // border pixel value, Integer
      COLORMAP = "color map", // color map, Long
      DEPTH = "visual depth", // depth, Integer
      VISUAL_CLASS = "visual class", // visual class, Integer
      VISUAL = "visual", // visual, Long
      EMBEDDED = "embedded", // is embedded?, Boolean
      DELAYED = "delayed", // is creation delayed?, Boolean
      PARENT = "parent", // parent peer
      BACKGROUND_PIXMAP = "pixmap", // background pixmap
      VISIBLE = "visible", // whether it is visible by default
      SAVE_UNDER = "save under", // save content under this window
      BACKING_STORE = "backing store", // enables double buffering
      BIT_GRAVITY = "bit gravity"; // copy old content on geometry change
  private XCreateWindowParams delayedParams;

  Set<Long> children = new HashSet<Long>();
  long window;
  boolean visible;
  boolean mapped;
  boolean embedded;
  Rectangle maxBounds;
  volatile XBaseWindow parentWindow;

  private boolean disposed;

  private long screen;
  private XSizeHints hints;
  private XWMHints wmHints;

  static final int MIN_SIZE = 1;
  static final int DEF_LOCATION = 1;

  private static XAtom wm_client_leader;

  static enum InitialiseState {
    INITIALISING,
    NOT_INITIALISED,
    INITIALISED,
    FAILED_INITIALISATION
  };

  private InitialiseState initialising;

  int x;
  int y;
  int width;
  int height;

  void awtLock() {
    XToolkit.awtLock();
  }

  void awtUnlock() {
    XToolkit.awtUnlock();
  }

  void awtLockNotifyAll() {
    XToolkit.awtLockNotifyAll();
  }

  void awtLockWait() throws InterruptedException {
    XToolkit.awtLockWait();
  }

  // To prevent errors from overriding obsolete methods
  protected final void init(long parentWindow, Rectangle bounds) {}

  protected final void preInit() {}

  protected final void postInit() {}

  // internal lock for synchronizing state changes and paint calls, initialized in preInit.
  // the order with other locks: AWTLock -> stateLock
  static class StateLock extends Object {}

  protected StateLock state_lock;

  /** Called for delayed inits during construction */
  void instantPreInit(XCreateWindowParams params) {
    state_lock = new StateLock();
    initialising = InitialiseState.NOT_INITIALISED;
  }

  /**
   * Called before window creation, descendants should override to initialize the data, initialize
   * params.
   */
  void preInit(XCreateWindowParams params) {
    state_lock = new StateLock();
    initialising = InitialiseState.NOT_INITIALISED;
    embedded = Boolean.TRUE.equals(params.get(EMBEDDED));
    visible = Boolean.TRUE.equals(params.get(VISIBLE));

    Object parent = params.get(PARENT);
    if (parent instanceof XBaseWindow) {
      parentWindow = (XBaseWindow) parent;
    } else {
      Long parentWindowID = (Long) params.get(PARENT_WINDOW);
      if (parentWindowID != null) {
        parentWindow = XToolkit.windowToXWindow(parentWindowID);
      }
    }

    Long eventMask = (Long) params.get(EVENT_MASK);
    if (eventMask != null) {
      long mask = eventMask.longValue();
      mask |= SubstructureNotifyMask;
      params.put(EVENT_MASK, mask);
    }

    screen = -1;
  }

  /**
   * Called after window creation, descendants should override to initialize Window with
   * class-specific values and perform post-initialization actions.
   */
  void postInit(XCreateWindowParams params) {
    if (log.isLoggable(Level.FINE)) log.fine("WM name is " + getWMName());
    updateWMName();

    // Set WM_CLIENT_LEADER property
    initClientLeader();
  }

  /**
   * Creates window using parameters <code>params</code> If params contain flag DELAYED doesn't do
   * anything. Note: Descendants can call this method to create the window at the time different to
   * instance construction.
   */
  protected final void init(XCreateWindowParams params) {
    awtLock();
    initialising = InitialiseState.INITIALISING;
    awtUnlock();

    try {
      if (!Boolean.TRUE.equals(params.get(DELAYED))) {
        preInit(params);
        create(params);
        postInit(params);
      } else {
        instantPreInit(params);
        delayedParams = params;
      }
      awtLock();
      initialising = InitialiseState.INITIALISED;
      awtLockNotifyAll();
      awtUnlock();
    } catch (RuntimeException re) {
      awtLock();
      initialising = InitialiseState.FAILED_INITIALISATION;
      awtLockNotifyAll();
      awtUnlock();
      throw re;
    } catch (Throwable t) {
      log.log(Level.WARNING, "Exception during peer initialization", t);
      awtLock();
      initialising = InitialiseState.FAILED_INITIALISATION;
      awtLockNotifyAll();
      awtUnlock();
    }
  }

  public boolean checkInitialised() {
    awtLock();
    try {
      switch (initialising) {
        case INITIALISED:
          return true;
        case INITIALISING:
          try {
            while (initialising != InitialiseState.INITIALISED) {
              awtLockWait();
            }
          } catch (InterruptedException ie) {
            return false;
          }
          return true;
        case NOT_INITIALISED:
        case FAILED_INITIALISATION:
          return false;
        default:
          return false;
      }
    } finally {
      awtUnlock();
    }
  }

  /*
   * Creates an invisible InputOnly window without an associated Component.
   */
  XBaseWindow() {
    this(new XCreateWindowParams());
  }

  /** Creates normal child window */
  XBaseWindow(long parentWindow, Rectangle bounds) {
    this(
        new XCreateWindowParams(
            new Object[] {BOUNDS, bounds, PARENT_WINDOW, Long.valueOf(parentWindow)}));
  }

  /** Creates top-level window */
  XBaseWindow(Rectangle bounds) {
    this(new XCreateWindowParams(new Object[] {BOUNDS, bounds}));
  }

  public XBaseWindow(XCreateWindowParams params) {
    init(params);
  }

  /* This create is used by the XEmbeddedFramePeer since it has to create the window
  as a child of the netscape window. This netscape window is passed in as wid */
  XBaseWindow(long parentWindow) {
    this(
        new XCreateWindowParams(
            new Object[] {PARENT_WINDOW, Long.valueOf(parentWindow), EMBEDDED, Boolean.TRUE}));
  }

  /**
   * Verifies that all required parameters are set. If not, sets them to default values. Verifies
   * values of critical parameters, adjust their values when needed.
   *
   * @throws IllegalArgumentException if params is null
   */
  protected void checkParams(XCreateWindowParams params) {
    if (params == null) {
      throw new IllegalArgumentException("Window creation parameters are null");
    }
    params.putIfNull(PARENT_WINDOW, Long.valueOf(XToolkit.getDefaultRootWindow()));
    params.putIfNull(BOUNDS, new Rectangle(DEF_LOCATION, DEF_LOCATION, MIN_SIZE, MIN_SIZE));
    params.putIfNull(DEPTH, Integer.valueOf((int) XlibWrapper.CopyFromParent));
    params.putIfNull(VISUAL, Long.valueOf(XlibWrapper.CopyFromParent));
    params.putIfNull(VISUAL_CLASS, Integer.valueOf((int) XlibWrapper.InputOnly));
    params.putIfNull(VALUE_MASK, Long.valueOf(XlibWrapper.CWEventMask));
    Rectangle bounds = (Rectangle) params.get(BOUNDS);
    bounds.width = Math.max(MIN_SIZE, bounds.width);
    bounds.height = Math.max(MIN_SIZE, bounds.height);

    Long eventMaskObj = (Long) params.get(EVENT_MASK);
    long eventMask = eventMaskObj != null ? eventMaskObj.longValue() : 0;
    // We use our own synthetic grab see XAwtState.getGrabWindow()
    // (see X vol. 1, 8.3.3.2)
    eventMask |= PropertyChangeMask | OwnerGrabButtonMask;
    params.put(EVENT_MASK, Long.valueOf(eventMask));
  }

  /**
   * Creates window with parameters specified by <code>params</code>
   *
   * @see #init
   */
  private final void create(XCreateWindowParams params) {
    XToolkit.awtLock();
    try {
      XSetWindowAttributes xattr = new XSetWindowAttributes();
      try {
        checkParams(params);

        long value_mask = ((Long) params.get(VALUE_MASK)).longValue();

        Long eventMask = (Long) params.get(EVENT_MASK);
        xattr.set_event_mask(eventMask.longValue());
        value_mask |= XlibWrapper.CWEventMask;

        Long border_pixel = (Long) params.get(BORDER_PIXEL);
        if (border_pixel != null) {
          xattr.set_border_pixel(border_pixel.longValue());
          value_mask |= XlibWrapper.CWBorderPixel;
        }

        Long colormap = (Long) params.get(COLORMAP);
        if (colormap != null) {
          xattr.set_colormap(colormap.longValue());
          value_mask |= XlibWrapper.CWColormap;
        }
        Long background_pixmap = (Long) params.get(BACKGROUND_PIXMAP);
        if (background_pixmap != null) {
          xattr.set_background_pixmap(background_pixmap.longValue());
          value_mask |= XlibWrapper.CWBackPixmap;
        }

        Long parentWindow = (Long) params.get(PARENT_WINDOW);
        Rectangle bounds = (Rectangle) params.get(BOUNDS);
        Integer depth = (Integer) params.get(DEPTH);
        Integer visual_class = (Integer) params.get(VISUAL_CLASS);
        Long visual = (Long) params.get(VISUAL);
        Boolean overrideRedirect = (Boolean) params.get(OVERRIDE_REDIRECT);
        if (overrideRedirect != null) {
          xattr.set_override_redirect(overrideRedirect.booleanValue());
          value_mask |= XlibWrapper.CWOverrideRedirect;
        }

        Boolean saveUnder = (Boolean) params.get(SAVE_UNDER);
        if (saveUnder != null) {
          xattr.set_save_under(saveUnder.booleanValue());
          value_mask |= XlibWrapper.CWSaveUnder;
        }

        Integer backingStore = (Integer) params.get(BACKING_STORE);
        if (backingStore != null) {
          xattr.set_backing_store(backingStore.intValue());
          value_mask |= XlibWrapper.CWBackingStore;
        }

        Integer bitGravity = (Integer) params.get(BIT_GRAVITY);
        if (bitGravity != null) {
          xattr.set_bit_gravity(bitGravity.intValue());
          value_mask |= XlibWrapper.CWBitGravity;
        }

        if (log.isLoggable(Level.FINE)) {
          log.fine("Creating window for " + this + " with the following attributes: \n" + params);
        }
        window =
            XlibWrapper.XCreateWindow(
                XToolkit.getDisplay(),
                parentWindow.longValue(),
                bounds.x,
                bounds.y, // location
                bounds.width,
                bounds.height, // size
                0, // border
                depth.intValue(), // depth
                visual_class.intValue(), // class
                visual.longValue(), // visual
                value_mask, // value mask
                xattr.pData); // attributes

        if (window == 0) {
          throw new IllegalStateException(
              "Couldn't create window because of wrong parameters. Run with NOISY_AWT to see details");
        }
        XToolkit.addToWinMap(window, this);
      } finally {
        xattr.dispose();
      }
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public XCreateWindowParams getDelayedParams() {
    return delayedParams;
  }

  protected String getWMName() {
    return XToolkit.getCorrectXIDString(getClass().getName());
  }

  protected void initClientLeader() {
    XToolkit.awtLock();
    try {
      if (wm_client_leader == null) {
        wm_client_leader = XAtom.get("WM_CLIENT_LEADER");
      }
      wm_client_leader.setWindowProperty(this, getXAWTRootWindow());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  static XRootWindow getXAWTRootWindow() {
    return XRootWindow.getInstance();
  }

  void destroy() {
    XToolkit.awtLock();
    try {
      if (hints != null) {
        XlibWrapper.XFree(hints.pData);
        hints = null;
      }
      XToolkit.removeFromWinMap(getWindow(), this);
      XlibWrapper.XDestroyWindow(XToolkit.getDisplay(), getWindow());
      if (XPropertyCache.isCachingSupported()) {
        XPropertyCache.clearCache(window);
      }
      window = -1;
      if (!isDisposed()) {
        setDisposed(true);
      }

      XAwtState
          .getGrabWindow(); // Magic - getGrabWindow clear state if grabbing window is disposed of.
    } finally {
      XToolkit.awtUnlock();
    }
  }

  void flush() {
    XToolkit.awtLock();
    try {
      XlibWrapper.XFlush(XToolkit.getDisplay());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  /** Helper function to set W */
  public final void setWMHints(XWMHints hints) {
    XToolkit.awtLock();
    try {
      XlibWrapper.XSetWMHints(XToolkit.getDisplay(), getWindow(), hints.pData);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public XWMHints getWMHints() {
    if (wmHints == null) {
      wmHints = new XWMHints(XlibWrapper.XAllocWMHints());
      //              XlibWrapper.XGetWMHints(XToolkit.getDisplay(),
      //                                      getWindow(),
      //                                      wmHints.pData);
    }
    return wmHints;
  }

  /*
   * Call this method under AWTLock.
   * The lock should be acquired untill all operations with XSizeHints are completed.
   */
  public XSizeHints getHints() {
    if (hints == null) {
      long p_hints = XlibWrapper.XAllocSizeHints();
      hints = new XSizeHints(p_hints);
      //              XlibWrapper.XGetWMNormalHints(XToolkit.getDisplay(), getWindow(), p_hints,
      // XlibWrapper.larg1);
      // TODO: Shouldn't we listen for WM updates on this property?
    }
    return hints;
  }

  public void setSizeHints(long flags, int x, int y, int width, int height) {
    if (insLog.isLoggable(Level.FINER))
      insLog.finer("Setting hints, flags " + XlibWrapper.hintsToString(flags));
    XToolkit.awtLock();
    try {
      XSizeHints hints = getHints();
      // Note: if PPosition is not set in flags this means that
      // we want to reset PPosition in hints.  This is necessary
      // for locationByPlatform functionality
      if ((flags & XlibWrapper.PPosition) != 0) {
        hints.set_x(x);
        hints.set_y(y);
      }
      if ((flags & XlibWrapper.PSize) != 0) {
        hints.set_width(width);
        hints.set_height(height);
      } else if ((hints.get_flags() & XlibWrapper.PSize) != 0) {
        flags |= XlibWrapper.PSize;
      }
      if ((flags & XlibWrapper.PMinSize) != 0) {
        hints.set_min_width(width);
        hints.set_min_height(height);
      } else if ((hints.get_flags() & XlibWrapper.PMinSize) != 0) {
        flags |= XlibWrapper.PMinSize;
        // Fix for 4320050: Minimum size for java.awt.Frame is not being enforced.
        // We don't need to reset minimum size if it's already set
      }
      if ((flags & XlibWrapper.PMaxSize) != 0) {
        if (maxBounds != null) {
          if (maxBounds.width != Integer.MAX_VALUE) {
            hints.set_max_width(maxBounds.width);
          } else {
            hints.set_max_width(XToolkit.getDefaultScreenWidth());
          }
          if (maxBounds.height != Integer.MAX_VALUE) {
            hints.set_max_height(maxBounds.height);
          } else {
            hints.set_max_height(XToolkit.getDefaultScreenHeight());
          }
        } else {
          hints.set_max_width(width);
          hints.set_max_height(height);
        }
      } else if ((hints.get_flags() & XlibWrapper.PMaxSize) != 0) {
        flags |= XlibWrapper.PMaxSize;
        if (maxBounds != null) {
          if (maxBounds.width != Integer.MAX_VALUE) {
            hints.set_max_width(maxBounds.width);
          } else {
            hints.set_max_width(XToolkit.getDefaultScreenWidth());
          }
          if (maxBounds.height != Integer.MAX_VALUE) {
            hints.set_max_height(maxBounds.height);
          } else {
            hints.set_max_height(XToolkit.getDefaultScreenHeight());
          }
        } else {
          // Leave intact
        }
      }
      flags |= XlibWrapper.PWinGravity;
      hints.set_flags(flags);
      hints.set_win_gravity((int) XlibWrapper.NorthWestGravity);
      if (insLog.isLoggable(Level.FINER))
        insLog.finer(
            "Setting hints, resulted flags "
                + XlibWrapper.hintsToString(flags)
                + ", values "
                + hints);
      XlibWrapper.XSetWMNormalHints(XToolkit.getDisplay(), getWindow(), hints.pData);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public boolean isMinSizeSet() {
    XSizeHints hints = getHints();
    long flags = hints.get_flags();
    return ((flags & XlibWrapper.PMinSize) == XlibWrapper.PMinSize);
  }

  /**
   * This lock object can be used to protect instance data from concurrent access by two threads. If
   * both state lock and AWT lock are taken, AWT Lock should be taken first.
   */
  Object getStateLock() {
    return state_lock;
  }

  public long getWindow() {
    return window;
  }

  public long getContentWindow() {
    return window;
  }

  public XBaseWindow getContentXWindow() {
    return XToolkit.windowToXWindow(getContentWindow());
  }

  public Rectangle getBounds() {
    return new Rectangle(x, y, width, height);
  }

  public Dimension getSize() {
    return new Dimension(width, height);
  }

  public void toFront() {
    XToolkit.awtLock();
    try {
      XlibWrapper.XRaiseWindow(XToolkit.getDisplay(), getWindow());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public void xRequestFocus(long time) {
    XToolkit.awtLock();
    try {
      if (focusLog.isLoggable(Level.FINER))
        focusLog.finer("XSetInputFocus on " + Long.toHexString(getWindow()) + " with time " + time);
      XlibWrapper.XSetInputFocus2(XToolkit.getDisplay(), getWindow(), time);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public void xRequestFocus() {
    XToolkit.awtLock();
    try {
      if (focusLog.isLoggable(Level.FINER))
        focusLog.finer("XSetInputFocus on " + Long.toHexString(getWindow()));
      XlibWrapper.XSetInputFocus(XToolkit.getDisplay(), getWindow());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public static long xGetInputFocus() {
    XToolkit.awtLock();
    try {
      return XlibWrapper.XGetInputFocus(XToolkit.getDisplay());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  public void xSetVisible(boolean visible) {
    if (log.isLoggable(Level.FINE)) log.fine("Setting visible on " + this + " to " + visible);
    XToolkit.awtLock();
    try {
      this.visible = visible;
      if (visible) {
        XlibWrapper.XMapWindow(XToolkit.getDisplay(), getWindow());
      } else {
        XlibWrapper.XUnmapWindow(XToolkit.getDisplay(), getWindow());
      }
      XlibWrapper.XFlush(XToolkit.getDisplay());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  boolean isMapped() {
    return mapped;
  }

  void updateWMName() {
    String name = getWMName();
    XToolkit.awtLock();
    try {
      if (name == null) {
        name = " ";
      }
      XAtom nameAtom = XAtom.get(XAtom.XA_WM_NAME);
      nameAtom.setProperty(getWindow(), name);
      XAtom netNameAtom = XAtom.get("_NET_WM_NAME");
      netNameAtom.setPropertyUTF8(getWindow(), name);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  void setWMClass(String[] cl) {
    if (cl.length != 2) {
      throw new IllegalArgumentException("WM_CLASS_NAME consists of exactly two strings");
    }
    XToolkit.awtLock();
    try {
      XAtom xa = XAtom.get(XAtom.XA_WM_CLASS);
      xa.setProperty8(getWindow(), cl[0] + '\0' + cl[1]);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  boolean isVisible() {
    return visible;
  }

  static long getScreenOfWindow(long window) {
    XToolkit.awtLock();
    try {
      return XlibWrapper.getScreenOfWindow(XToolkit.getDisplay(), window);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  long getScreenNumber() {
    XToolkit.awtLock();
    try {
      return XlibWrapper.XScreenNumberOfScreen(getScreen());
    } finally {
      XToolkit.awtUnlock();
    }
  }

  long getScreen() {
    if (screen == -1) { // Not initialized
      screen = getScreenOfWindow(window);
    }
    return screen;
  }

  public void xSetBounds(Rectangle bounds) {
    xSetBounds(bounds.x, bounds.y, bounds.width, bounds.height);
  }

  public void xSetBounds(int x, int y, int width, int height) {
    if (getWindow() == 0) {
      insLog.warning("Attempt to resize uncreated window");
      throw new IllegalStateException("Attempt to resize uncreated window");
    }
    insLog.fine(
        "Setting bounds on " + this + " to (" + x + ", " + y + "), " + width + "x" + height);
    if (width <= 0) {
      width = 1;
    }
    if (height <= 0) {
      height = 1;
    }
    XToolkit.awtLock();
    try {
      XlibWrapper.XMoveResizeWindow(XToolkit.getDisplay(), getWindow(), x, y, width, height);
    } finally {
      XToolkit.awtUnlock();
    }
  }

  /**
   * Translate coordinates from one window into another. Optimized for XAWT - uses cached data when
   * possible. Preferable over pure XTranslateCoordinates.
   *
   * @return coordinates relative to dst, or null if error happened
   */
  static Point toOtherWindow(long src, long dst, int x, int y) {
    Point rpt = new Point(0, 0);

    // Check if both windows belong to XAWT - then no X calls are necessary

    XBaseWindow srcPeer = XToolkit.windowToXWindow(src);
    XBaseWindow dstPeer = XToolkit.windowToXWindow(dst);

    if (srcPeer != null && dstPeer != null) {
      // (x, y) is relative to src
      rpt.x = x + srcPeer.getAbsoluteX() - dstPeer.getAbsoluteX();
      rpt.y = y + srcPeer.getAbsoluteY() - dstPeer.getAbsoluteY();
    } else if (dstPeer != null && XlibUtil.isRoot(src, dstPeer.getScreenNumber())) {
      // from root into peer
      rpt.x = x - dstPeer.getAbsoluteX();
      rpt.y = y - dstPeer.getAbsoluteY();
    } else if (srcPeer != null && XlibUtil.isRoot(dst, srcPeer.getScreenNumber())) {
      // from peer into root
      rpt.x = x + srcPeer.getAbsoluteX();
      rpt.y = y + srcPeer.getAbsoluteY();
    } else {
      rpt = XlibUtil.translateCoordinates(src, dst, new Point(x, y));
    }
    return rpt;
  }

  /*
   * Convert to global coordinates.
   */
  Rectangle toGlobal(Rectangle rec) {
    Point p = toGlobal(rec.getLocation());
    Rectangle newRec = new Rectangle(rec);
    if (p != null) {
      newRec.setLocation(p);
    }
    return newRec;
  }

  Point toGlobal(Point pt) {
    Point p = toGlobal(pt.x, pt.y);
    if (p != null) {
      return p;
    } else {
      return new Point(pt);
    }
  }

  Point toGlobal(int x, int y) {
    long root;
    XToolkit.awtLock();
    try {
      root = XlibWrapper.RootWindow(XToolkit.getDisplay(), getScreenNumber());
    } finally {
      XToolkit.awtUnlock();
    }
    Point p = toOtherWindow(getContentWindow(), root, x, y);
    if (p != null) {
      return p;
    } else {
      return new Point(x, y);
    }
  }

  /*
   * Convert to local coordinates.
   */
  Point toLocal(Point pt) {
    Point p = toLocal(pt.x, pt.y);
    if (p != null) {
      return p;
    } else {
      return new Point(pt);
    }
  }

  Point toLocal(int x, int y) {
    long root;
    XToolkit.awtLock();
    try {
      root = XlibWrapper.RootWindow(XToolkit.getDisplay(), getScreenNumber());
    } finally {
      XToolkit.awtUnlock();
    }
    Point p = toOtherWindow(root, getContentWindow(), x, y);
    if (p != null) {
      return p;
    } else {
      return new Point(x, y);
    }
  }

  /**
   * We should always grab both keyboard and pointer to control event flow on popups. This also
   * simplifies synthetic grab implementation. The active grab overrides activated automatic grab.
   */
  public boolean grabInput() {
    if (grabLog.isLoggable(Level.FINE)) {
      grabLog.log(Level.FINE, "Grab input on {0}", new Object[] {String.valueOf(this)});
    }

    XToolkit.awtLock();
    try {
      if (XAwtState.getGrabWindow() == this && XAwtState.isManualGrab()) {
        grabLog.fine("    Already Grabbed");
        return true;
      }
      // 6273031: PIT. Choice drop down does not close once it is right clicked to show a popup menu
      // remember previous window having grab and if it's not null ungrab it.
      XBaseWindow prevGrabWindow = XAwtState.getGrabWindow();
      final int eventMask =
          (int)
              (ButtonPressMask
                  | ButtonReleaseMask
                  | EnterWindowMask
                  | LeaveWindowMask
                  | PointerMotionMask
                  | ButtonMotionMask);
      final int ownerEvents = 1;

      int ptrGrab =
          XlibWrapper.XGrabPointer(
              XToolkit.getDisplay(),
              getContentWindow(),
              ownerEvents,
              eventMask,
              GrabModeAsync,
              GrabModeAsync,
              None,
              (XWM.isMotif() ? XToolkit.arrowCursor : None),
              CurrentTime);
      // Check grab results to be consistent with X server grab
      if (ptrGrab != GrabSuccess) {
        XlibWrapper.XUngrabPointer(XToolkit.getDisplay(), CurrentTime);
        XAwtState.setGrabWindow(null);
        grabLog.fine("    Grab Failure - mouse");
        return false;
      }

      int keyGrab =
          XlibWrapper.XGrabKeyboard(
              XToolkit.getDisplay(),
              getContentWindow(),
              ownerEvents,
              GrabModeAsync,
              GrabModeAsync,
              CurrentTime);
      if (keyGrab != GrabSuccess) {
        XlibWrapper.XUngrabPointer(XToolkit.getDisplay(), CurrentTime);
        XlibWrapper.XUngrabKeyboard(XToolkit.getDisplay(), CurrentTime);
        XAwtState.setGrabWindow(null);
        grabLog.fine("    Grab Failure - keyboard");
        return false;
      }
      if (prevGrabWindow != null) {
        prevGrabWindow.ungrabInputImpl();
      }
      XAwtState.setGrabWindow(this);
      grabLog.fine("    Grab - success");
      return true;
    } finally {
      XToolkit.awtUnlock();
    }
  }

  static void ungrabInput() {
    XToolkit.awtLock();
    try {
      XBaseWindow grabWindow = XAwtState.getGrabWindow();
      if (grabLog.isLoggable(Level.FINE)) {
        grabLog.log(Level.FINE, "UnGrab input on {0}", new Object[] {String.valueOf(grabWindow)});
      }
      if (grabWindow != null) {
        grabWindow.ungrabInputImpl();
        XlibWrapper.XUngrabPointer(XToolkit.getDisplay(), CurrentTime);
        XlibWrapper.XUngrabKeyboard(XToolkit.getDisplay(), CurrentTime);
        XAwtState.setGrabWindow(null);
        // we need to call XFlush() here to force ungrab
        // see 6384219 for details
        XlibWrapper.XFlush(XToolkit.getDisplay());
      }
    } finally {
      XToolkit.awtUnlock();
    }
  }

  // called from ungrabInput, used in popup windows to hide theirselfs in ungrabbing
  void ungrabInputImpl() {}

  static void checkSecurity() {
    if (XToolkit.isSecurityWarningEnabled() && XToolkit.isToolkitThread()) {
      StackTraceElement stack[] = (new Throwable()).getStackTrace();
      log.warning(stack[1] + ": Security violation: calling user code on toolkit thread");
    }
  }

  public Set<Long> getChildren() {
    synchronized (getStateLock()) {
      return new HashSet<Long>(children);
    }
  }

  // -------------- Event handling ----------------
  public void handleMapNotifyEvent(XEvent xev) {
    mapped = true;
  }

  public void handleUnmapNotifyEvent(XEvent xev) {
    mapped = false;
  }

  public void handleReparentNotifyEvent(XEvent xev) {
    if (eventLog.isLoggable(Level.FINER)) {
      XReparentEvent msg = xev.get_xreparent();
      eventLog.finer(msg.toString());
    }
  }

  public void handlePropertyNotify(XEvent xev) {
    XPropertyEvent msg = xev.get_xproperty();
    if (XPropertyCache.isCachingSupported()) {
      XPropertyCache.clearCache(window, XAtom.get(msg.get_atom()));
    }
    if (eventLog.isLoggable(Level.FINER)) {
      eventLog.log(Level.FINER, "{0}", new Object[] {String.valueOf(msg)});
    }
  }

  public void handleDestroyNotify(XEvent xev) {
    XAnyEvent xany = xev.get_xany();
    if (xany.get_window() == getWindow()) {
      XToolkit.removeFromWinMap(getWindow(), this);
      if (XPropertyCache.isCachingSupported()) {
        XPropertyCache.clearCache(getWindow());
      }
    }
    if (xany.get_window() != getWindow()) {
      synchronized (getStateLock()) {
        children.remove(xany.get_window());
      }
    }
  }

  public void handleCreateNotify(XEvent xev) {
    XAnyEvent xany = xev.get_xany();
    if (xany.get_window() != getWindow()) {
      synchronized (getStateLock()) {
        children.add(xany.get_window());
      }
    }
  }

  public void handleClientMessage(XEvent xev) {
    if (eventLog.isLoggable(Level.FINER)) {
      XClientMessageEvent msg = xev.get_xclient();
      eventLog.finer(msg.toString());
    }
  }

  public void handleVisibilityEvent(XEvent xev) {}

  public void handleKeyPress(XEvent xev) {}

  public void handleKeyRelease(XEvent xev) {}

  public void handleExposeEvent(XEvent xev) {}
  /** Activate automatic grab on first ButtonPress, deactivate on full mouse release */
  public void handleButtonPressRelease(XEvent xev) {
    XButtonEvent xbe = xev.get_xbutton();
    final int buttonState =
        xbe.get_state() & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask);
    switch (xev.get_type()) {
      case ButtonPress:
        if (buttonState == 0) {
          XAwtState.setAutoGrabWindow(this);
        }
        break;
      case ButtonRelease:
        if (isFullRelease(buttonState, xbe.get_button())) {
          XAwtState.setAutoGrabWindow(null);
        }
        break;
    }
  }

  public void handleMotionNotify(XEvent xev) {}

  public void handleXCrossingEvent(XEvent xev) {}

  public void handleConfigureNotifyEvent(XEvent xev) {
    XConfigureEvent xe = xev.get_xconfigure();
    if (insLog.isLoggable(Level.FINER)) {
      insLog.log(Level.FINER, "Configure, {0}", new Object[] {String.valueOf(xe)});
    }
    x = xe.get_x();
    y = xe.get_y();
    width = xe.get_width();
    height = xe.get_height();
  }
  /** Checks ButtonRelease released all Mouse buttons */
  static boolean isFullRelease(int buttonState, int button) {
    switch (button) {
      case Button1:
        return buttonState == Button1Mask;
      case Button2:
        return buttonState == Button2Mask;
      case Button3:
        return buttonState == Button3Mask;
      case Button4:
        return buttonState == Button4Mask;
      case Button5:
        return buttonState == Button5Mask;
    }
    return buttonState == 0;
  }

  static boolean isGrabbedEvent(XEvent ev, XBaseWindow target) {
    switch (ev.get_type()) {
      case ButtonPress:
      case ButtonRelease:
      case MotionNotify:
      case KeyPress:
      case KeyRelease:
        return true;
      case LeaveNotify:
      case EnterNotify:
        // We shouldn't dispatch this events to the grabbed components (see 6317481)
        // But this logic is important if the grabbed component is top-level (see realSync)
        return (target instanceof XWindowPeer);
      default:
        return false;
    }
  }
  /**
   * Dispatches event to the grab Window or event source window depending on whether the grab is
   * active and on the event type
   */
  static void dispatchToWindow(XEvent ev) {
    XBaseWindow target = XAwtState.getGrabWindow();
    if (target == null || !isGrabbedEvent(ev, target)) {
      target = XToolkit.windowToXWindow(ev.get_xany().get_window());
    }
    if (target != null && target.checkInitialised()) {
      target.dispatchEvent(ev);
    }
  }

  public void dispatchEvent(XEvent xev) {
    if (eventLog.isLoggable(Level.FINEST)) eventLog.finest(xev.toString());
    int type = xev.get_type();

    if (isDisposed()) {
      return;
    }

    switch (type) {
      case VisibilityNotify:
        handleVisibilityEvent(xev);
        break;
      case ClientMessage:
        handleClientMessage(xev);
        break;
      case Expose:
      case GraphicsExpose:
        handleExposeEvent(xev);
        break;
      case ButtonPress:
      case ButtonRelease:
        handleButtonPressRelease(xev);
        break;

      case MotionNotify:
        handleMotionNotify(xev);
        break;
      case KeyPress:
        handleKeyPress(xev);
        break;
      case KeyRelease:
        handleKeyRelease(xev);
        break;
      case EnterNotify:
      case LeaveNotify:
        handleXCrossingEvent(xev);
        break;
      case ConfigureNotify:
        handleConfigureNotifyEvent(xev);
        break;
      case MapNotify:
        handleMapNotifyEvent(xev);
        break;
      case UnmapNotify:
        handleUnmapNotifyEvent(xev);
        break;
      case ReparentNotify:
        handleReparentNotifyEvent(xev);
        break;
      case PropertyNotify:
        handlePropertyNotify(xev);
        break;
      case DestroyNotify:
        handleDestroyNotify(xev);
        break;
      case CreateNotify:
        handleCreateNotify(xev);
        break;
    }
  }

  protected boolean isEventDisabled(XEvent e) {
    return false;
  }

  int getX() {
    return x;
  }

  int getY() {
    return y;
  }

  int getWidth() {
    return width;
  }

  int getHeight() {
    return height;
  }

  void setDisposed(boolean d) {
    disposed = d;
  }

  boolean isDisposed() {
    return disposed;
  }

  public int getAbsoluteX() {
    XBaseWindow pw = getParentWindow();
    if (pw != null) {
      return pw.getAbsoluteX() + getX();
    } else {
      // Overridden for top-levels as their (x,y) is Java (x, y), not native location
      return getX();
    }
  }

  public int getAbsoluteY() {
    XBaseWindow pw = getParentWindow();
    if (pw != null) {
      return pw.getAbsoluteY() + getY();
    } else {
      return getY();
    }
  }

  public XBaseWindow getParentWindow() {
    return parentWindow;
  }

  public XWindowPeer getToplevelXWindow() {
    XBaseWindow bw = this;
    while (bw != null && !(bw instanceof XWindowPeer)) {
      bw = bw.getParentWindow();
    }
    return (XWindowPeer) bw;
  }

  public String toString() {
    return super.toString() + "(" + Long.toString(getWindow(), 16) + ")";
  }

  /** Returns whether the given point is inside of the window. Coordinates are local. */
  public boolean contains(int x, int y) {
    return x >= 0 && y >= 0 && x < getWidth() && y < getHeight();
  }

  /** Returns whether the given point is inside of the window. Coordinates are global. */
  public boolean containsGlobal(int x, int y) {
    return x >= getAbsoluteX()
        && y >= getAbsoluteY()
        && x < (getAbsoluteX() + getWidth())
        && y < (getAbsoluteY() + getHeight());
  }
}
Exemple #24
0
/**
 * Provides capabilities to a specific <code>JComponent</code> to contain <code>PluginComponent
 * </code>s, track when they are added and removed.
 *
 * @author Lyubomir Marinov
 */
public class PluginContainer implements PluginComponentListener {
  /**
   * The <tt>Logger</tt> used by the <tt>PluginContainer</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(PluginContainer.class);

  /**
   * The <code>JComponent</code> which contains the components of the <code>PluginComponent</code>s
   * managed by this instance.
   */
  private final JComponent container;

  /** The container id of the <code>PluginComponent</code> managed by this instance. */
  private final Container containerId;

  /**
   * The list of <code>PluginComponent</code> instances which have their components added to this
   * <code>PluginContainer</code>.
   */
  private final java.util.List<PluginComponent> pluginComponents =
      new LinkedList<PluginComponent>();

  /**
   * Initializes a new <code>PluginContainer</code> instance which is to provide capabilities to a
   * specific <code>JComponent</code> container with a specific <code>Container</code> id to contain
   * <code>PluginComponent</code> and track when they are added and removed.
   *
   * @param container the <code>JComponent</code> container the new instance is to provide its
   *     capabilities to
   * @param containerId the <code>Container</code> id of the specified <code>container</code>
   */
  public PluginContainer(JComponent container, Container containerId) {
    this.container = container;
    this.containerId = containerId;

    initPluginComponents();
  }

  /**
   * Adds a specific <tt>Component</tt> to a specific <tt>JComponent</tt> container. Allows
   * extenders to apply custom logic to the exact placement of the specified <tt>Component</tt> in
   * the specified container.
   *
   * @param component the <tt>Component</tt> to be added to the specified <tt>JComponent</tt>
   *     container
   * @param container the <tt>JComponent</tt> container to add the specified <tt>Component</tt> to
   * @param preferredIndex the index at which <tt>component</tt> is to be added to
   *     <tt>container</tt> if possible or <tt>-1</tt> if there is no preference with respect to the
   *     index in question
   */
  protected void addComponentToContainer(
      Component component, JComponent container, int preferredIndex) {
    if ((0 <= preferredIndex) && (preferredIndex < getComponentCount(container)))
      container.add(component, preferredIndex);
    else container.add(component);
  }

  /**
   * Adds the component of a specific <tt>PluginComponent</tt> to the associated <tt>Container</tt>.
   *
   * @param c the <tt>PluginComponent</tt> which is to have its component added to the
   *     <tt>Container</tt> associated with this <tt>PluginContainer</tt>
   */
  private synchronized void addPluginComponent(PluginComponent c) {
    /*
     * Try to respect positionIndex of PluginComponent to some extent:
     * PluginComponents with positionIndex equal to 0 go at the beginning,
     * these with positionIndex equal to -1 follow them and then go these
     * with positionIndex greater than 0.
     */
    int cIndex = c.getPositionIndex();
    int index = -1;
    int i = 0;

    for (PluginComponent pluginComponent : pluginComponents) {
      if (pluginComponent.equals(c)) return;

      if (-1 == index) {
        int pluginComponentIndex = pluginComponent.getPositionIndex();

        if ((0 == cIndex) || (-1 == cIndex)) {
          if ((0 != pluginComponentIndex) && (cIndex != pluginComponentIndex)) index = i;
        } else if (cIndex < pluginComponentIndex) index = i;
      }

      i++;
    }

    int pluginComponentCount = pluginComponents.size();

    if (-1 == index) index = pluginComponents.size();

    /*
     * The container may have added Components of its own apart from the
     * ones this PluginContainer has added to it. Since the common case for
     * the additional Components is to have them appear at the beginning,
     * adjust the index so it gets correct in the common case.
     */
    int containerComponentCount = getComponentCount(container);

    addComponentToContainer(
        (Component) c.getComponent(),
        container,
        (containerComponentCount > pluginComponentCount)
            ? (index + (containerComponentCount - pluginComponentCount))
            : index);
    pluginComponents.add(index, c);

    container.revalidate();
    container.repaint();
  }

  /**
   * Runs clean-up for associated resources which need explicit disposal (e.g. listeners keeping
   * this instance alive because they were added to the model which operationally outlives this
   * instance).
   */
  public void dispose() {
    GuiActivator.getUIService().removePluginComponentListener(this);

    /*
     * Explicitly remove the components of the PluginComponent instances
     * because the latter are registered with OSGi and are thus global.
     */
    synchronized (this) {
      for (PluginComponent pluginComponent : pluginComponents)
        container.remove((Component) pluginComponent.getComponent());
      pluginComponents.clear();
    }
  }

  /**
   * Gets the number of <tt>Component</tt>s in a specific <tt>JComponent</tt> container. For
   * example, returns the result of <tt>getMenuComponentCount()</tt> if <tt>container</tt> is an
   * instance of <tt>JMenu</tt>.
   *
   * @param container the <tt>JComponent</tt> container to get the number of <tt>Component</tt>s of
   * @return the number of <tt>Component</tt>s in the specified <tt>container</tt>
   */
  protected int getComponentCount(JComponent container) {
    return (container instanceof JMenu)
        ? ((JMenu) container).getMenuComponentCount()
        : container.getComponentCount();
  }

  /**
   * Gets the <tt>PluginComponent</tt>s of this <tt>PluginContainer</tt>.
   *
   * @return an <tt>Iterable</tt> over the <tt>PluginComponent</tt>s of this
   *     <tt>PluginContainer</tt>
   */
  public Iterable<PluginComponent> getPluginComponents() {
    return pluginComponents;
  }

  /**
   * Adds the <tt>Component</tt>s of the <tt>PluginComponent</tt>s registered in the OSGi
   * <tt>BundleContext</tt> in the associated <tt>Container</tt>.
   */
  private void initPluginComponents() {
    // Look for PluginComponents registered in the OSGi BundleContext.
    ServiceReference[] serRefs = null;

    try {
      serRefs =
          GuiActivator.bundleContext.getServiceReferences(
              PluginComponent.class.getName(),
              "(" + Container.CONTAINER_ID + "=" + containerId.getID() + ")");
    } catch (InvalidSyntaxException exc) {
      logger.error("Could not obtain plugin reference.", exc);
    }

    if (serRefs != null) {
      for (ServiceReference serRef : serRefs) {
        PluginComponent component = (PluginComponent) GuiActivator.bundleContext.getService(serRef);

        addPluginComponent(component);
      }
    }

    GuiActivator.getUIService().addPluginComponentListener(this);
  }

  /**
   * Implements {@link PluginComponentListener#pluginComponentAdded(PluginComponentEvent)}.
   *
   * @param event a <tt>PluginComponentEvent</tt> which specifies the <tt>PluginComponent</tt> which
   *     has been added
   */
  public void pluginComponentAdded(PluginComponentEvent event) {
    PluginComponent c = event.getPluginComponent();

    if (c.getContainer().equals(containerId)) addPluginComponent(c);
  }

  /**
   * Implements {@link PluginComponentListener#pluginComponentRemoved(PluginComponentEvent)}.
   *
   * @param event a <tt>PluginComponentEvent</tt> which specifies the <tt>PluginComponent</tt> which
   *     has been added
   */
  public void pluginComponentRemoved(PluginComponentEvent event) {
    PluginComponent c = event.getPluginComponent();

    if (c.getContainer().equals(containerId)) removePluginComponent(c);
  }

  /**
   * Removes the component of a specific <code>PluginComponent</code> from this <code>
   * PluginContainer</code>.
   *
   * @param c the <code>PluginComponent</code> which is to have its component removed from this
   *     <code>PluginContainer</code>
   */
  private synchronized void removePluginComponent(PluginComponent c) {
    container.remove((Component) c.getComponent());
    pluginComponents.remove(c);
  }
}
Exemple #25
0
public class ShowPreviewDialog extends SIPCommDialog
    implements ActionListener, ChatLinkClickedListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 1L;

  /**
   * The <tt>Logger</tt> used by the <tt>ShowPreviewDialog</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(ShowPreviewDialog.class);

  ConfigurationService cfg = GuiActivator.getConfigurationService();

  /** The Ok button. */
  private final JButton okButton;

  /** The cancel button. */
  private final JButton cancelButton;

  /** Checkbox that indicates whether or not to show this dialog next time. */
  private final JCheckBox enableReplacementProposal;

  /** Checkbox that indicates whether or not to show previews automatically */
  private final JCheckBox enableReplacement;

  /** The <tt>ChatConversationPanel</tt> that this dialog is associated with. */
  private final ChatConversationPanel chatPanel;

  /** Mapping between messageID and the string representation of the chat message. */
  private Map<String, String> msgIDToChatString = new ConcurrentHashMap<String, String>();

  /**
   * Mapping between the pair (messageID, link position) and the actual link in the string
   * representation of the chat message.
   */
  private Map<String, String> msgIDandPositionToLink = new ConcurrentHashMap<String, String>();

  /**
   * Mapping between link and replacement for this link that is acquired from it's corresponding
   * <tt>ReplacementService</tt>.
   */
  private Map<String, String> linkToReplacement = new ConcurrentHashMap<String, String>();

  /** The id of the message that is currently associated with this dialog. */
  private String currentMessageID = "";

  /** The position of the link in the current message. */
  private String currentLinkPosition = "";

  /**
   * Creates an instance of <tt>ShowPreviewDialog</tt>
   *
   * @param chatPanel The <tt>ChatConversationPanel</tt> that is associated with this dialog.
   */
  ShowPreviewDialog(final ChatConversationPanel chatPanel) {
    this.chatPanel = chatPanel;

    this.setTitle(
        GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_DIALOG_TITLE"));
    okButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.OK"));
    cancelButton = new JButton(GuiActivator.getResources().getI18NString("service.gui.CANCEL"));

    JPanel mainPanel = new TransparentPanel();
    mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
    mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    // mainPanel.setPreferredSize(new Dimension(200, 150));
    this.getContentPane().add(mainPanel);

    JTextPane descriptionMsg = new JTextPane();
    descriptionMsg.setEditable(false);
    descriptionMsg.setOpaque(false);
    descriptionMsg.setText(
        GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW_WARNING_DESCRIPTION"));

    Icon warningIcon = null;
    try {
      warningIcon =
          new ImageIcon(
              ImageIO.read(
                  GuiActivator.getResources().getImageURL("service.gui.icons.WARNING_ICON")));
    } catch (IOException e) {
      logger.debug("failed to load the warning icon");
    }
    JLabel warningSign = new JLabel(warningIcon);

    JPanel warningPanel = new TransparentPanel();
    warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS));
    warningPanel.add(warningSign);
    warningPanel.add(Box.createHorizontalStrut(10));
    warningPanel.add(descriptionMsg);

    enableReplacement =
        new JCheckBox(
            GuiActivator.getResources()
                .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_STATUS"));
    enableReplacement.setOpaque(false);
    enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true));
    enableReplacementProposal =
        new JCheckBox(
            GuiActivator.getResources()
                .getI18NString("plugin.chatconfig.replacement.ENABLE_REPLACEMENT_PROPOSAL"));
    enableReplacementProposal.setOpaque(false);

    JPanel checkBoxPanel = new TransparentPanel();
    checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS));
    checkBoxPanel.add(enableReplacement);
    checkBoxPanel.add(enableReplacementProposal);

    JPanel buttonsPanel = new TransparentPanel(new FlowLayout(FlowLayout.CENTER));
    buttonsPanel.add(okButton);
    buttonsPanel.add(cancelButton);

    mainPanel.add(warningPanel);
    mainPanel.add(Box.createVerticalStrut(10));
    mainPanel.add(checkBoxPanel);
    mainPanel.add(buttonsPanel);

    okButton.addActionListener(this);
    cancelButton.addActionListener(this);

    this.setPreferredSize(new Dimension(390, 230));
  }

  @Override
  public void actionPerformed(ActionEvent arg0) {
    if (arg0.getSource().equals(okButton)) {
      cfg.setProperty(ReplacementProperty.REPLACEMENT_ENABLE, enableReplacement.isSelected());
      cfg.setProperty(
          ReplacementProperty.REPLACEMENT_PROPOSAL, enableReplacementProposal.isSelected());
      SwingWorker worker =
          new SwingWorker() {
            /**
             * Called on the event dispatching thread (not on the worker thread) after the <code>
             * construct</code> method has returned.
             */
            @Override
            public void finished() {
              String newChatString = (String) get();

              if (newChatString != null) {
                try {
                  Element elem = chatPanel.document.getElement(currentMessageID);
                  chatPanel.document.setOuterHTML(elem, newChatString);
                  msgIDToChatString.put(currentMessageID, newChatString);
                } catch (BadLocationException ex) {
                  logger.error("Could not replace chat message", ex);
                } catch (IOException ex) {
                  logger.error("Could not replace chat message", ex);
                }
              }
            }

            @Override
            protected Object construct() throws Exception {
              String newChatString = msgIDToChatString.get(currentMessageID);
              try {
                String originalLink =
                    msgIDandPositionToLink.get(currentMessageID + "#" + currentLinkPosition);
                String replacementLink = linkToReplacement.get(originalLink);
                String replacement;
                DirectImageReplacementService source =
                    GuiActivator.getDirectImageReplacementSource();
                if (originalLink.equals(replacementLink)
                    && (!source.isDirectImage(originalLink)
                        || source.getImageSize(originalLink) == -1)) {
                  replacement = originalLink;
                } else {
                  replacement =
                      "<IMG HEIGHT=\"90\" WIDTH=\"120\" SRC=\""
                          + replacementLink
                          + "\" BORDER=\"0\" ALT=\""
                          + originalLink
                          + "\"></IMG>";
                }

                String old =
                    originalLink
                        + "</A> <A href=\"jitsi://"
                        + ShowPreviewDialog.this.getClass().getName()
                        + "/SHOWPREVIEW?"
                        + currentMessageID
                        + "#"
                        + currentLinkPosition
                        + "\">"
                        + GuiActivator.getResources().getI18NString("service.gui.SHOW_PREVIEW");

                newChatString = newChatString.replace(old, replacement);
              } catch (Exception ex) {
                logger.error("Could not replace chat message", ex);
              }
              return newChatString;
            }
          };
      worker.start();
      this.setVisible(false);
    } else if (arg0.getSource().equals(cancelButton)) {
      this.setVisible(false);
    }
  }

  @Override
  public void chatLinkClicked(URI url) {
    String action = url.getPath();
    if (action.equals("/SHOWPREVIEW")) {
      enableReplacement.setSelected(cfg.getBoolean(ReplacementProperty.REPLACEMENT_ENABLE, true));
      enableReplacementProposal.setSelected(
          cfg.getBoolean(ReplacementProperty.REPLACEMENT_PROPOSAL, true));

      currentMessageID = url.getQuery();
      currentLinkPosition = url.getFragment();

      this.setVisible(true);
      this.setLocationRelativeTo(chatPanel);
    }
  }

  /**
   * Returns mapping between messageID and the string representation of the chat message.
   *
   * @return mapping between messageID and chat string.
   */
  Map<String, String> getMsgIDToChatString() {
    return msgIDToChatString;
  }

  /**
   * Returns mapping between the pair (messageID, link position) and the actual link in the string
   * representation of the chat message.
   *
   * @return mapping between (messageID, linkPosition) and link.
   */
  Map<String, String> getMsgIDandPositionToLink() {
    return msgIDandPositionToLink;
  }

  /**
   * Returns mapping between link and replacement for this link that was acquired from it's
   * corresponding <tt>ReplacementService</tt>.
   *
   * @return mapping between link and it's corresponding replacement.
   */
  Map<String, String> getLinkToReplacement() {
    return linkToReplacement;
  }
}
Exemple #26
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;
    }
  }
}
/**
 * The stream used by JMF for our image streaming.
 *
 * @author Sebastien Vincent
 * @author Lyubomir Marinov
 * @author Damian Minkov
 */
public class ImageStream extends AbstractVideoPullBufferStream<DataSource> {
  /**
   * The <tt>Logger</tt> used by the <tt>ImageStream</tt> class and its instances for logging
   * output.
   */
  private static final Logger logger = Logger.getLogger(ImageStream.class);

  /**
   * The pool of <tt>ByteBuffer</tt>s this instances is using to optimize the allocations and
   * deallocations of <tt>ByteBuffer</tt>s.
   */
  private final ByteBufferPool byteBufferPool = new ByteBufferPool();

  /** Desktop interaction (screen capture, key press, ...). */
  private DesktopInteract desktopInteract = null;

  /** Index of display that we will capture from. */
  private int displayIndex = -1;

  /** Sequence number. */
  private long seqNo = 0;

  /** X origin. */
  private int x = 0;

  /** Y origin. */
  private int y = 0;

  /**
   * Initializes a new <tt>ImageStream</tt> instance which is to have a specific
   * <tt>FormatControl</tt>
   *
   * @param dataSource the <tt>DataSource</tt> which is creating the new instance so that it becomes
   *     one of its <tt>streams</tt>
   * @param formatControl the <tt>FormatControl</tt> of the new instance which is to specify the
   *     format in which it is to provide its media data
   */
  ImageStream(DataSource dataSource, FormatControl formatControl) {
    super(dataSource, formatControl);
  }

  /**
   * Blocks and reads into a <tt>Buffer</tt> from this <tt>PullBufferStream</tt>.
   *
   * @param buffer the <tt>Buffer</tt> this <tt>PullBufferStream</tt> is to read into
   * @throws IOException if an I/O error occurs while this <tt>PullBufferStream</tt> reads into the
   *     specified <tt>Buffer</tt>
   * @see AbstractVideoPullBufferStream#doRead(Buffer)
   */
  @Override
  protected void doRead(Buffer buffer) throws IOException {
    /*
     * Determine the Format in which we're expected to output. We cannot
     * rely on the Format always being specified in the Buffer because it is
     * not its responsibility, the DataSource of this ImageStream knows the
     * output Format.
     */
    Format format = buffer.getFormat();

    if (format == null) {
      format = getFormat();
      if (format != null) buffer.setFormat(format);
    }

    if (format instanceof AVFrameFormat) {
      Object o = buffer.getData();
      AVFrame frame;

      if (o instanceof AVFrame) frame = (AVFrame) o;
      else {
        frame = new AVFrame();
        buffer.setData(frame);
      }

      AVFrameFormat avFrameFormat = (AVFrameFormat) format;
      Dimension size = avFrameFormat.getSize();
      ByteBuffer data = readScreenNative(size);

      if (data != null) {
        if (frame.avpicture_fill(data, avFrameFormat) < 0) {
          data.free();
          throw new IOException("avpicture_fill");
        }
      } else {
        /*
         * This can happen when we disconnect a monitor from computer
         * before or during grabbing.
         */
        throw new IOException("Failed to grab screen.");
      }
    } else {
      byte[] bytes = (byte[]) buffer.getData();
      Dimension size = ((VideoFormat) format).getSize();

      bytes = readScreen(bytes, size);

      buffer.setData(bytes);
      buffer.setOffset(0);
      buffer.setLength(bytes.length);
    }

    buffer.setHeader(null);
    buffer.setTimeStamp(System.nanoTime());
    buffer.setSequenceNumber(seqNo);
    buffer.setFlags(Buffer.FLAG_SYSTEM_TIME | Buffer.FLAG_LIVE_DATA);
    seqNo++;
  }

  /**
   * Read screen.
   *
   * @param output output buffer for screen bytes
   * @param dim dimension of the screen
   * @return raw bytes, it could be equal to output or not. Take care in the caller to check if
   *     output is the returned value.
   */
  public byte[] readScreen(byte[] output, Dimension dim) {
    VideoFormat format = (VideoFormat) getFormat();
    Dimension formatSize = format.getSize();
    int width = formatSize.width;
    int height = formatSize.height;
    BufferedImage scaledScreen = null;
    BufferedImage screen = null;
    byte data[] = null;
    int size = width * height * 4;

    // If output is not large enough, enlarge it.
    if ((output == null) || (output.length < size)) output = new byte[size];

    /* get desktop screen via native grabber if available */
    if (desktopInteract.captureScreen(displayIndex, x, y, dim.width, dim.height, output)) {
      return output;
    }

    System.out.println("failed to grab with native! " + output.length);

    /* OK native grabber failed or is not available,
     * try with AWT Robot and convert it to the right format
     *
     * Note that it is very memory consuming since memory are allocated
     * to capture screen (via Robot) and then for converting to raw bytes
     * Moreover support for multiple display has not yet been investigated
     *
     * Normally not of our supported platform (Windows (x86, x64),
     * Linux (x86, x86-64), Mac OS X (i386, x86-64, ppc) and
     * FreeBSD (x86, x86-64) should go here.
     */
    screen = desktopInteract.captureScreen();

    if (screen != null) {
      /* convert to ARGB BufferedImage */
      scaledScreen =
          ImgStreamingUtils.getScaledImage(screen, width, height, BufferedImage.TYPE_INT_ARGB);
      /* get raw bytes */
      data = ImgStreamingUtils.getImageBytes(scaledScreen, output);
    }

    screen = null;
    scaledScreen = null;
    return data;
  }

  /**
   * Read screen and store result in native buffer.
   *
   * @param dim dimension of the video
   * @return true if success, false otherwise
   */
  private ByteBuffer readScreenNative(Dimension dim) {
    int size = dim.width * dim.height * 4 + FFmpeg.FF_INPUT_BUFFER_PADDING_SIZE;
    ByteBuffer data = byteBufferPool.getBuffer(size);

    data.setLength(size);

    /* get desktop screen via native grabber */
    boolean b;

    try {
      b =
          desktopInteract.captureScreen(
              displayIndex, x, y, dim.width, dim.height, data.getPtr(), data.getLength());
    } catch (Throwable t) {
      if (t instanceof ThreadDeath) {
        throw (ThreadDeath) t;
      } else {
        b = false;
        //                logger.error("Failed to grab screen!", t);
      }
    }
    if (!b) {
      data.free();
      data = null;
    }
    return data;
  }

  /**
   * Sets the index of the display to be used by this <tt>ImageStream</tt>.
   *
   * @param displayIndex the index of the display to be used by this <tt>ImageStream</tt>
   */
  public void setDisplayIndex(int displayIndex) {
    this.displayIndex = displayIndex;
  }

  /**
   * Sets the origin to be captured by this <tt>ImageStream</tt>.
   *
   * @param x the x coordinate of the origin to be set on this instance
   * @param y the y coordinate of the origin to be set on this instance
   */
  public void setOrigin(int x, int y) {
    this.x = x;
    this.y = y;
  }

  /**
   * Start desktop capture stream.
   *
   * @see AbstractPullBufferStream#start()
   */
  @Override
  public void start() throws IOException {
    super.start();

    if (desktopInteract == null) {
      try {
        desktopInteract = new DesktopInteractImpl();
      } catch (Exception e) {
        logger.warn("Cannot create DesktopInteract object!");
      }
    }
  }

  /**
   * Stop desktop capture stream.
   *
   * @see AbstractPullBufferStream#stop()
   */
  @Override
  public void stop() throws IOException {
    try {
      if (logger.isInfoEnabled()) logger.info("Stop stream");
    } finally {
      super.stop();

      byteBufferPool.drain();
    }
  }
}
/**
 * The Dialog Class
 *
 * @author Frank Kunz The dialog class draws the basic dialog with a grid layout. The dialog
 *     consists of three main parts. A settings panel, a table panel and three buttons.
 */
public final class UARTProtocolAnalysisDialog extends BaseToolDialog<UARTDataSet>
    implements ExportAware<UARTDataSet> {
  // INNER TYPES

  /** Provides a combobox renderer for {@link UARTParity} values. */
  static final class UARTParityItemRenderer extends EnumItemRenderer<Parity> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final Parity aValue) {
      String text = super.getDisplayValue(aValue);
      if (Parity.EVEN.equals(aValue)) {
        text = "Even parity";
      } else if (Parity.NONE.equals(aValue)) {
        text = "No parity";
      } else if (Parity.ODD.equals(aValue)) {
        text = "Odd parity";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link UARTStopBits} values. */
  static final class UARTStopBitsItemRenderer extends EnumItemRenderer<StopBits> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final StopBits aValue) {
      String text = super.getDisplayValue(aValue);
      if (StopBits.ONE.equals(aValue)) {
        text = "1";
      } else if (StopBits.ONE_HALF.equals(aValue)) {
        text = "1.5";
      } else if (StopBits.TWO.equals(aValue)) {
        text = "2";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitOrder} values. */
  static final class UARTBitOrderItemRenderer extends EnumItemRenderer<BitOrder> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitOrder aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitOrder.LSB_FIRST.equals(aValue)) {
        text = "LSB first";
      } else if (BitOrder.MSB_FIRST.equals(aValue)) {
        text = "MSB first";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitEncoding} values. */
  static final class UARTBitEncodingItemRenderer extends EnumItemRenderer<BitEncoding> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitEncoding aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitEncoding.HIGH_IS_MARK.equals(aValue)) {
        text = "High is mark (1)";
      } else if (BitEncoding.HIGH_IS_SPACE.equals(aValue)) {
        text = "High is space (0)";
      }
      return text;
    }
  }

  /** Provides a combobox renderer for {@link BitLevel} values. */
  static final class UARTIdleLevelItemRenderer extends EnumItemRenderer<BitLevel> {
    // CONSTANTS

    private static final long serialVersionUID = 1L;

    // METHODS

    /**
     * @see
     *     nl.lxtreme.ols.client.diagram.settings.GeneralSettingsDialog.EnumItemRenderer#getDisplayValue(java.lang.Enum)
     */
    @Override
    protected String getDisplayValue(final BitLevel aValue) {
      String text = super.getDisplayValue(aValue);
      if (BitLevel.HIGH.equals(aValue)) {
        text = "High (start = L, stop = H)";
      } else if (BitLevel.LOW.equals(aValue)) {
        text = "Low (start = H, stop = L)";
      }
      return text;
    }
  }

  // CONSTANTS

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = Logger.getLogger(UARTProtocolAnalysisDialog.class.getName());

  // VARIABLES

  private JComboBox rxd;
  private JComboBox txd;
  private JComboBox cts;
  private JComboBox rts;
  private JComboBox dtr;
  private JComboBox dsr;
  private JComboBox dcd;
  private JComboBox ri;
  private JComboBox parity;
  private JComboBox bits;
  private JComboBox stop;
  private JComboBox bitEncoding;
  private JComboBox bitOrder;
  private JComboBox idleLevel;
  private JCheckBox autoDetectBaudRate;
  private JComboBox baudrate;
  private JEditorPane outText;

  private RestorableAction runAnalysisAction;
  private Action closeAction;
  private Action exportAction;

  // CONSTRUCTORS

  /**
   * Creates a new UARTProtocolAnalysisDialog instance.
   *
   * @param aOwner the owner of this dialog;
   * @param aToolContext the tool context;
   * @param aContext the OSGi bundle context to use;
   * @param aTool the {@link UARTAnalyser} tool.
   */
  public UARTProtocolAnalysisDialog(
      final Window aOwner,
      final ToolContext aToolContext,
      final BundleContext aContext,
      final UARTAnalyser aTool) {
    super(aOwner, aToolContext, aContext, aTool);

    initDialog();

    setLocationRelativeTo(getOwner());
  }

  // METHODS

  /** {@inheritDoc} */
  @Override
  public void exportToFile(
      final File aOutputFile, final nl.lxtreme.ols.tool.base.ExportAware.ExportFormat aFormat)
      throws IOException {
    if (ExportFormat.HTML.equals(aFormat)) {
      storeToHtmlFile(aOutputFile, getLastResult());
    } else if (ExportFormat.CSV.equals(aFormat)) {
      storeToCsvFile(aOutputFile, getLastResult());
    }
  }

  /** {@inheritDoc} */
  @Override
  public void readPreferences(final UserSettings aSettings) {
    // Issue #114: avoid setting illegal values...
    setComboBoxIndex(this.rxd, aSettings, "rxd");
    setComboBoxIndex(this.txd, aSettings, "txd");
    setComboBoxIndex(this.cts, aSettings, "cts");
    setComboBoxIndex(this.rts, aSettings, "rts");
    setComboBoxIndex(this.dtr, aSettings, "dtr");
    setComboBoxIndex(this.dsr, aSettings, "dsr");
    setComboBoxIndex(this.dcd, aSettings, "dcd");
    setComboBoxIndex(this.ri, aSettings, "ri");

    this.parity.setSelectedIndex(aSettings.getInt("parity", this.parity.getSelectedIndex()));
    this.bits.setSelectedIndex(aSettings.getInt("bits", this.bits.getSelectedIndex()));
    this.stop.setSelectedIndex(aSettings.getInt("stop", this.stop.getSelectedIndex()));
    this.idleLevel.setSelectedIndex(
        aSettings.getInt("idle-state", this.idleLevel.getSelectedIndex()));
    this.bitEncoding.setSelectedIndex(
        aSettings.getInt("bit-encoding", this.bitEncoding.getSelectedIndex()));
    this.bitOrder.setSelectedIndex(aSettings.getInt("bit-order", this.bitOrder.getSelectedIndex()));
    this.baudrate.setSelectedItem(Integer.valueOf(aSettings.getInt("baudrate", 9600)));
    this.autoDetectBaudRate.setSelected(
        aSettings.getBoolean("auto-baudrate", this.autoDetectBaudRate.isSelected()));
  }

  /** {@inheritDoc} */
  @Override
  public void reset() {
    this.outText.setText(getEmptyHtmlPage());
    this.outText.setEditable(false);

    this.runAnalysisAction.restore();

    setControlsEnabled(true);

    this.exportAction.setEnabled(false);
  }

  /** @see nl.lxtreme.ols.api.Configurable#writePreferences(nl.lxtreme.ols.api.UserSettings) */
  @Override
  public void writePreferences(final UserSettings aSettings) {
    aSettings.putInt("rxd", this.rxd.getSelectedIndex());
    aSettings.putInt("txd", this.txd.getSelectedIndex());
    aSettings.putInt("cts", this.cts.getSelectedIndex());
    aSettings.putInt("rts", this.rts.getSelectedIndex());
    aSettings.putInt("dtr", this.dtr.getSelectedIndex());
    aSettings.putInt("dsr", this.dsr.getSelectedIndex());
    aSettings.putInt("dcd", this.dcd.getSelectedIndex());
    aSettings.putInt("ri", this.ri.getSelectedIndex());
    aSettings.putInt("parity", this.parity.getSelectedIndex());
    aSettings.putInt("bits", this.bits.getSelectedIndex());
    aSettings.putInt("stop", this.stop.getSelectedIndex());
    aSettings.putInt("idle-state", this.idleLevel.getSelectedIndex());
    aSettings.putInt("bit-encoding", this.bitEncoding.getSelectedIndex());
    aSettings.putInt("bit-order", this.bitOrder.getSelectedIndex());
    aSettings.putInt("baudrate", ((Integer) this.baudrate.getSelectedItem()).intValue());
    aSettings.putBoolean("auto-baudrate", this.autoDetectBaudRate.isSelected());
  }

  /** {@inheritDoc} */
  @Override
  protected void onToolEnded(final UARTDataSet aAnalysisResult) {
    try {
      final String htmlPage;
      if (aAnalysisResult != null) {
        htmlPage = toHtmlPage(null /* aFile */, aAnalysisResult);
      } else {
        htmlPage = getEmptyHtmlPage();
      }

      this.outText.setText(htmlPage);
      this.outText.setEditable(false);

      this.runAnalysisAction.restore();
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        // Should not happen in this situation!
        throw new RuntimeException(exception);
      }
    }
  }

  /** {@inheritDoc} */
  @Override
  protected void onToolStarted() {
    // NO-op
  }

  /** {@inheritDoc} */
  @Override
  protected void prepareToolTask(final ToolTask<UARTDataSet> aToolTask) {
    final UARTAnalyserTask toolTask = (UARTAnalyserTask) aToolTask;

    // The value at index zero is "Unused", so extracting one of all items
    // causes all "unused" values to be equivalent to -1, which is interpreted
    // as not used...
    toolTask.setRxdIndex(this.rxd.getSelectedIndex() - 1);
    toolTask.setTxdIndex(this.txd.getSelectedIndex() - 1);
    toolTask.setCtsIndex(this.cts.getSelectedIndex() - 1);
    toolTask.setRtsIndex(this.rts.getSelectedIndex() - 1);
    toolTask.setDcdIndex(this.dcd.getSelectedIndex() - 1);
    toolTask.setRiIndex(this.ri.getSelectedIndex() - 1);
    toolTask.setDsrIndex(this.dsr.getSelectedIndex() - 1);
    toolTask.setDtrIndex(this.dtr.getSelectedIndex() - 1);
    // Handle the auto detect option for baudrates...
    if (this.autoDetectBaudRate.isSelected()) {
      toolTask.setBaudRate(UARTAnalyserTask.AUTO_DETECT_BAUDRATE);
    } else {
      toolTask.setBaudRate(((Integer) this.baudrate.getSelectedItem()).intValue());
    }

    // Other properties...
    toolTask.setIdleLevel((BitLevel) this.idleLevel.getSelectedItem());
    toolTask.setBitEncoding((BitEncoding) this.bitEncoding.getSelectedItem());
    toolTask.setBitOrder((BitOrder) this.bitOrder.getSelectedItem());
    toolTask.setParity((Parity) this.parity.getSelectedItem());
    toolTask.setStopBits((StopBits) this.stop.getSelectedItem());
    toolTask.setBitCount(NumberUtils.smartParseInt((String) this.bits.getSelectedItem(), 8));
  }

  /**
   * set the controls of the dialog enabled/disabled
   *
   * @param aEnable status of the controls
   */
  @Override
  protected void setControlsEnabled(final boolean aEnable) {
    this.rxd.setEnabled(aEnable);
    this.txd.setEnabled(aEnable);
    this.cts.setEnabled(aEnable);
    this.rts.setEnabled(aEnable);
    this.dtr.setEnabled(aEnable);
    this.dsr.setEnabled(aEnable);
    this.dcd.setEnabled(aEnable);
    this.ri.setEnabled(aEnable);
    this.parity.setEnabled(aEnable);
    this.bits.setEnabled(aEnable);
    this.stop.setEnabled(aEnable);
    this.idleLevel.setEnabled(aEnable);
    this.bitEncoding.setEnabled(aEnable);
    this.bitOrder.setEnabled(aEnable);

    this.closeAction.setEnabled(aEnable);
    this.exportAction.setEnabled(aEnable);
  }

  /**
   * Creates the HTML template for exports to HTML.
   *
   * @param aExporter the HTML exporter instance to use, cannot be <code>null</code>.
   * @return a HTML exporter filled with the template, never <code>null</code>.
   */
  private HtmlExporter createHtmlTemplate(final HtmlExporter aExporter) {
    aExporter.addCssStyle("body { font-family: sans-serif; } ");
    aExporter.addCssStyle(
        "table { border-width: 1px; border-spacing: 0px; border-color: gray;"
            + " border-collapse: collapse; border-style: solid; margin-bottom: 15px; } ");
    aExporter.addCssStyle(
        "table th { border-width: 1px; padding: 2px; border-style: solid; border-color: gray;"
            + " background-color: #C0C0FF; text-align: left; font-weight: bold; font-family: sans-serif; } ");
    aExporter.addCssStyle(
        "table td { border-width: 1px; padding: 2px; border-style: solid; border-color: gray;"
            + " font-family: monospace; } ");
    aExporter.addCssStyle(".error { color: red; } ");
    aExporter.addCssStyle(".warning { color: orange; } ");
    aExporter.addCssStyle(".date { text-align: right; font-size: x-small; margin-bottom: 15px; } ");
    aExporter.addCssStyle(".w100 { width: 100%; } ");
    aExporter.addCssStyle(".w35 { width: 35%; } ");
    aExporter.addCssStyle(".w30 { width: 30%; } ");
    aExporter.addCssStyle(".w15 { width: 15%; } ");
    aExporter.addCssStyle(".w10 { width: 10%; } ");
    aExporter.addCssStyle(".w8 { width: 8%; } ");
    aExporter.addCssStyle(".w7 { width: 7%; } ");

    final Element body = aExporter.getBody();
    body.addChild(H1).addContent("UART Analysis results");
    body.addChild(HR);
    body.addChild(DIV).addAttribute("class", "date").addContent("Generated: ", "{date-now}");

    Element table, tr, thead, tbody;

    table = body.addChild(TABLE).addAttribute("class", "w100");

    tbody = table.addChild(TBODY);
    tr = tbody.addChild(TR);
    tr.addChild(TH).addAttribute("colspan", "2").addContent("Statistics");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Decoded bytes");
    tr.addChild(TD).addContent("{decoded-bytes}");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Detected bus errors");
    tr.addChild(TD).addContent("{detected-bus-errors}");
    tr = tbody.addChild(TR);
    tr.addChild(TD).addAttribute("class", "w30").addContent("Baudrate");
    tr.addChild(TD).addContent("{baudrate}");

    table = body.addChild(TABLE).addAttribute("class", "w100");
    thead = table.addChild(THEAD);
    tr = thead.addChild(TR);
    tr.addChild(TH).addAttribute("class", "w30").addAttribute("colspan", "2");
    tr.addChild(TH).addAttribute("class", "w35").addAttribute("colspan", "4").addContent("RxD");
    tr.addChild(TH).addAttribute("class", "w35").addAttribute("colspan", "4").addContent("TxD");
    tr = thead.addChild(TR);
    tr.addChild(TH).addAttribute("class", "w15").addContent("Index");
    tr.addChild(TH).addAttribute("class", "w15").addContent("Time");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Hex");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Bin");
    tr.addChild(TH).addAttribute("class", "w8").addContent("Dec");
    tr.addChild(TH).addAttribute("class", "w7").addContent("ASCII");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Hex");
    tr.addChild(TH).addAttribute("class", "w10").addContent("Bin");
    tr.addChild(TH).addAttribute("class", "w8").addContent("Dec");
    tr.addChild(TH).addAttribute("class", "w7").addContent("ASCII");
    tbody = table.addChild(TBODY);
    tbody.addContent("{decoded-data}");

    return aExporter;
  }

  /** @return */
  private JPanel createPreviewPane() {
    final JPanel panTable = new JPanel(new GridLayout(1, 1, 0, 0));

    this.outText = new JEditorPane("text/html", getEmptyHtmlPage());
    this.outText.setEditable(false);

    panTable.add(new JScrollPane(this.outText));

    return panTable;
  }

  /** @return */
  private JPanel createSettingsPane() {
    final int channelCount = getData().getChannels();

    final Integer[] baudrates = new Integer[AsyncSerialDataDecoder.COMMON_BAUDRATES.length];
    for (int i = 0; i < baudrates.length; i++) {
      baudrates[i] = Integer.valueOf(AsyncSerialDataDecoder.COMMON_BAUDRATES[i]);
    }
    final String[] bitarray = new String[10];
    // allow symbol lengths between 5 and 14 bits...
    for (int i = 0; i < bitarray.length; i++) {
      bitarray[i] = String.format("%d", Integer.valueOf(i + 5));
    }

    final JPanel settings = new JPanel(new SpringLayout());

    SpringLayoutUtils.addSeparator(settings, "Settings");

    settings.add(createRightAlignedLabel("RxD"));
    this.rxd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.rxd);

    settings.add(createRightAlignedLabel("TxD"));
    this.txd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.txd);

    settings.add(createRightAlignedLabel("CTS"));
    this.cts = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.cts);

    settings.add(createRightAlignedLabel("RTS"));
    this.rts = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.rts);

    settings.add(createRightAlignedLabel("DTR"));
    this.dtr = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dtr);

    settings.add(createRightAlignedLabel("DSR"));
    this.dsr = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dsr);

    settings.add(createRightAlignedLabel("DCD"));
    this.dcd = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.dcd);

    settings.add(createRightAlignedLabel("RI"));
    this.ri = SwingComponentUtils.createOptionalChannelSelector(channelCount);
    settings.add(this.ri);

    settings.add(createRightAlignedLabel("Baudrate"));
    this.autoDetectBaudRate = new JCheckBox("Auto detect");
    settings.add(this.autoDetectBaudRate);

    settings.add(new JLabel(""));
    this.baudrate = new JComboBox(baudrates);
    // Issue #90: allow custom baudrates to be specified...
    this.baudrate.setEditable(true);
    this.baudrate.setSelectedIndex(0);
    settings.add(this.baudrate);

    this.autoDetectBaudRate.addItemListener(
        new ItemListener() {
          @Override
          public void itemStateChanged(final ItemEvent aEvent) {
            final JCheckBox cb = (JCheckBox) aEvent.getSource();
            UARTProtocolAnalysisDialog.this.baudrate.setEnabled(!cb.isSelected());
          }
        });

    settings.add(createRightAlignedLabel("Parity"));
    this.parity = new JComboBox(Parity.values());
    this.parity.setSelectedIndex(0);
    this.parity.setRenderer(new UARTParityItemRenderer());
    settings.add(this.parity);

    settings.add(createRightAlignedLabel("Bits"));
    this.bits = new JComboBox(bitarray);
    this.bits.setSelectedIndex(3);
    settings.add(this.bits);

    settings.add(createRightAlignedLabel("Stopbits"));
    this.stop = new JComboBox(StopBits.values());
    this.stop.setSelectedIndex(0);
    this.stop.setRenderer(new UARTStopBitsItemRenderer());
    settings.add(this.stop);

    settings.add(createRightAlignedLabel("Idle level"));
    this.idleLevel = new JComboBox(BitLevel.values());
    this.idleLevel.setSelectedIndex(0);
    this.idleLevel.setRenderer(new UARTIdleLevelItemRenderer());
    settings.add(this.idleLevel);

    settings.add(createRightAlignedLabel("Bit encoding"));
    this.bitEncoding = new JComboBox(BitEncoding.values());
    this.bitEncoding.setSelectedIndex(0);
    this.bitEncoding.setRenderer(new UARTBitEncodingItemRenderer());
    settings.add(this.bitEncoding);

    settings.add(createRightAlignedLabel("Bit order"));
    this.bitOrder = new JComboBox(BitOrder.values());
    this.bitOrder.setSelectedIndex(0);
    this.bitOrder.setRenderer(new UARTBitOrderItemRenderer());
    settings.add(this.bitOrder);

    SpringLayoutUtils.makeEditorGrid(settings, 10, 4);

    return settings;
  }

  /**
   * generate a HTML page
   *
   * @param empty if this is true an empty output is generated
   * @return String with HTML data
   */
  private String getEmptyHtmlPage() {
    final HtmlExporter exporter = createHtmlTemplate(ExportUtils.createHtmlExporter());
    return exporter.toString(
        new MacroResolver() {
          @Override
          public Object resolve(final String aMacro, final Element aParent) {
            if ("date-now".equals(aMacro)) {
              final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
              return df.format(new Date());
            } else if ("decoded-bytes".equals(aMacro)
                || "detected-bus-errors".equals(aMacro)
                || "baudrate".equals(aMacro)) {
              return "-";
            } else if ("decoded-data".equals(aMacro)) {
              return null;
            }
            return null;
          }
        });
  }

  /** Initializes this dialog. */
  private void initDialog() {
    setMinimumSize(new Dimension(640, 480));

    final JComponent settingsPane = createSettingsPane();
    final JComponent previewPane = createPreviewPane();

    final JPanel contentPane = new JPanel(new GridBagLayout());
    contentPane.add(
        settingsPane,
        new GridBagConstraints(
            0,
            0,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.NORTH,
            GridBagConstraints.NONE,
            new Insets(2, 0, 2, 0),
            0,
            0));
    contentPane.add(
        previewPane,
        new GridBagConstraints(
            1,
            0,
            1,
            1,
            1.0,
            1.0,
            GridBagConstraints.NORTH,
            GridBagConstraints.BOTH,
            new Insets(2, 0, 2, 0),
            0,
            0));

    final JButton runAnalysisButton = ToolUtils.createRunAnalysisButton(this);
    this.runAnalysisAction = (RestorableAction) runAnalysisButton.getAction();

    final JButton exportButton = ToolUtils.createExportButton(this);
    this.exportAction = exportButton.getAction();
    this.exportAction.setEnabled(false);

    final JButton closeButton = ToolUtils.createCloseButton();
    this.closeAction = closeButton.getAction();

    final JComponent buttons =
        SwingComponentUtils.createButtonPane(runAnalysisButton, exportButton, closeButton);

    SwingComponentUtils.setupWindowContentPane(this, contentPane, buttons, runAnalysisButton);
  }

  /**
   * exports the data to a CSV file
   *
   * @param aFile File object
   */
  private void storeToCsvFile(final File aFile, final UARTDataSet aDataSet) {
    try {
      final CsvExporter exporter = ExportUtils.createCsvExporter(aFile);

      exporter.setHeaders(
          "index",
          "start-time",
          "end-time",
          "event?",
          "event-type",
          "RxD event",
          "TxD event",
          "RxD data",
          "TxD data");

      final List<UARTData> decodedData = aDataSet.getData();
      for (int i = 0; i < decodedData.size(); i++) {
        final UARTData ds = decodedData.get(i);

        final String startTime = Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex()));
        final String endTime = Unit.Time.format(aDataSet.getTime(ds.getEndSampleIndex()));

        String eventType = null;
        String rxdEvent = null;
        String txdEvent = null;
        String rxdData = null;
        String txdData = null;

        switch (ds.getType()) {
          case UARTData.UART_TYPE_EVENT:
            eventType = ds.getEventName();
            break;

          case UARTData.UART_TYPE_RXEVENT:
            rxdEvent = ds.getEventName();
            break;

          case UARTData.UART_TYPE_TXEVENT:
            txdEvent = ds.getEventName();
            break;

          case UARTData.UART_TYPE_RXDATA:
            rxdData = Integer.toString(ds.getData());
            break;

          case UARTData.UART_TYPE_TXDATA:
            txdData = Integer.toString(ds.getData());
            break;

          default:
            break;
        }

        exporter.addRow(
            Integer.valueOf(i),
            startTime,
            endTime,
            Boolean.valueOf(ds.isEvent()),
            eventType,
            rxdEvent,
            txdEvent,
            rxdData,
            txdData);
      }

      exporter.close();
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        LOG.log(Level.WARNING, "CSV export failed!", exception);
      }
    }
  }

  /**
   * stores the data to a HTML file
   *
   * @param aFile file object
   */
  private void storeToHtmlFile(final File aFile, final UARTDataSet aDataSet) {
    try {
      toHtmlPage(aFile, aDataSet);
    } catch (final IOException exception) {
      // Make sure to handle IO-interrupted exceptions properly!
      if (!HostUtils.handleInterruptedException(exception)) {
        LOG.log(Level.WARNING, "HTML export failed!", exception);
      }
    }
  }

  /**
   * generate a HTML page
   *
   * @param empty if this is true an empty output is generated
   * @return String with HTML data
   */
  private String toHtmlPage(final File aFile, final UARTDataSet aDataSet) throws IOException {
    final int bitCount = Integer.parseInt((String) this.bits.getSelectedItem());
    final int bitAdder = ((bitCount % 4) != 0) ? 1 : 0;

    final MacroResolver macroResolver =
        new MacroResolver() {
          @Override
          public Object resolve(final String aMacro, final Element aParent) {
            if ("date-now".equals(aMacro)) {
              final DateFormat df = DateFormat.getDateInstance(DateFormat.LONG);
              return df.format(new Date());
            } else if ("decoded-bytes".equals(aMacro)) {
              return Integer.valueOf(aDataSet.getDecodedSymbols());
            } else if ("detected-bus-errors".equals(aMacro)) {
              return Integer.valueOf(aDataSet.getDetectedErrors());
            } else if ("baudrate".equals(aMacro)) {
              final String baudrate;
              if (aDataSet.getBaudRate() <= 0) {
                baudrate = "<span class='error'>Baudrate calculation failed!</span>";
              } else {
                baudrate =
                    String.format(
                        "%d (exact: %d)",
                        Integer.valueOf(aDataSet.getBaudRate()),
                        Integer.valueOf(aDataSet.getBaudRateExact()));
                if (!aDataSet.isBitLengthUsable()) {
                  return baudrate.concat(
                      " <span class='warning'>The baudrate may be wrong, use a higher samplerate to avoid this!</span>");
                }

                return baudrate;
              }
            } else if ("decoded-data".equals(aMacro)) {
              final List<UARTData> decodedData = aDataSet.getData();
              Element tr;

              for (int i = 0; i < decodedData.size(); i++) {
                final UARTData ds = decodedData.get(i);

                if (ds.isEvent()) {
                  String rxEventData = "";
                  String txEventData = "";

                  String bgColor;
                  if (UARTData.UART_TYPE_EVENT == ds.getType()) {
                    rxEventData = txEventData = ds.getEventName();
                    bgColor = "#e0e0e0";
                  } else if (UARTData.UART_TYPE_RXEVENT == ds.getType()) {
                    rxEventData = ds.getEventName();
                    bgColor = "#c0ffc0";
                  } else if (UARTData.UART_TYPE_TXEVENT == ds.getType()) {
                    txEventData = ds.getEventName();
                    bgColor = "#c0ffc0";
                  } else {
                    // unknown event
                    bgColor = "#ff8000";
                  }

                  if (txEventData.endsWith("_ERR") || rxEventData.endsWith("_ERR")) {
                    bgColor = "#ff8000";
                  }

                  tr =
                      aParent
                          .addChild(TR)
                          .addAttribute("style", "background-color: " + bgColor + ";");
                  tr.addChild(TD).addContent(String.valueOf(i));
                  tr.addChild(TD)
                      .addContent(Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex())));
                  tr.addChild(TD).addContent(rxEventData);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD).addContent(txEventData);
                  tr.addChild(TD);
                  tr.addChild(TD);
                  tr.addChild(TD);
                } else {
                  String rxDataHex = "", rxDataBin = "", rxDataDec = "", rxDataASCII = "";
                  String txDataHex = "", txDataBin = "", txDataDec = "", txDataASCII = "";

                  // Normal data...
                  if (UARTData.UART_TYPE_RXDATA == ds.getType()) {
                    final int rxData = ds.getData();

                    rxDataHex = integerToHexString(rxData, (bitCount / 4) + bitAdder);
                    rxDataBin = integerToBinString(rxData, bitCount);
                    rxDataDec = String.valueOf(rxData);
                    rxDataASCII = toASCII((char) rxData);
                  } else
                  /* if ( UARTData.UART_TYPE_TXDATA == ds.getType() ) */
                  {
                    final int txData = ds.getData();

                    txDataHex = integerToHexString(txData, (bitCount / 4) + bitAdder);
                    txDataBin = integerToBinString(txData, bitCount);
                    txDataDec = String.valueOf(txData);
                    txDataASCII = toASCII(txData);
                  }

                  tr = aParent.addChild(TR);
                  tr.addChild(TD).addContent(String.valueOf(i));
                  tr.addChild(TD)
                      .addContent(Unit.Time.format(aDataSet.getTime(ds.getStartSampleIndex())));
                  tr.addChild(TD).addContent("0x", rxDataHex);
                  tr.addChild(TD).addContent("0b", rxDataBin);
                  tr.addChild(TD).addContent(rxDataDec);
                  tr.addChild(TD).addContent(rxDataASCII);
                  tr.addChild(TD).addContent("0x", txDataHex);
                  tr.addChild(TD).addContent("0b", txDataBin);
                  tr.addChild(TD).addContent(txDataDec);
                  tr.addChild(TD).addContent(txDataASCII);
                }
              }
            }
            return null;
          }
        };

    if (aFile == null) {
      final HtmlExporter exporter = createHtmlTemplate(ExportUtils.createHtmlExporter());
      return exporter.toString(macroResolver);
    } else {
      final HtmlFileExporter exporter =
          (HtmlFileExporter) createHtmlTemplate(ExportUtils.createHtmlExporter(aFile));
      exporter.write(macroResolver);
      exporter.close();
    }

    return null;
  }
}
/**
 * Dialog box to get configuration options for client application. This class provides a standard
 * dialog box which allows the user to select the location of the database (which may be a physical
 * file in local mode, or the address (and, optionally, the port) of the server. The user can, of
 * course, cancel, in which case the application should not start (this is at the applications
 * discretion though - the business logic could be changed later in the calling class to decide to
 * start the application anyway if there configuration info can be loaded from file).<br>
 */
public class DatabaseLocationDialog extends WindowAdapter implements ActionListener, Observer {
  /*
   * The strings for the title and buttons in the dialog box. While these
   * could be hard coded in the application, having them here makes it
   * easier to use internationalization options such as a ResourceBundle.
   */
  private static final String TITLE = "Please enter database location";
  private static final String CONNECT = "Connect";
  private static final String EXIT = "Exit";

  /*
   * Some values for possible port ranges so we can determine what sort of
   * port the user has specified.
   */
  private static final int LOWEST_PORT = 0;
  private static final int HIGHEST_PORT = 65535;
  private static final int SYSTEM_PORT_BOUNDARY = 1024;
  private static final int IANA_PORT_BOUNDARY = 49151;

  /*
   * The bits and pieces that comprise our dialog box. They are all global so
   * we can disable them or enable them as the user enters valid information.
   */
  private JOptionPane options = null;
  private JDialog dialog = null;
  private JButton connectButton = new JButton(CONNECT);
  private JButton exitButton = new JButton(EXIT);

  /*
   * The common panel that is used by both the client and the server for
   * specifying where the database is.
   */
  private ConfigOptions configOptions = null;

  /*
   * Flags to show whether enough information has been provided for us to
   * start the application.
   */
  private boolean validDb = false;
  private boolean validPort = false;
  private boolean validCnx = false;

  /*
   * Details specified in the configOptions pane detailing where the database
   * is.
   */
  private String location = null;
  private String port = null;
  private ConnectionType networkType = null;

  /*
   * The Logger instance. All log messages from this class are routed through
   * this member. The Logger namespace is <code>sampleproject.gui</code>.
   */
  private Logger log = Logger.getLogger("sampleproject.gui");

  /**
   * Creates a dialog where the user can specify the location of the database,including the type of
   * network connection (if this is a networked client)and IP address and port number; or search and
   * select the database on a local drive if this is a standalone client.
   *
   * @param parent Defines the Component that is to be the parent of this dialog box. For
   *     information on how this is used, see <code>JOptionPane</code>
   * @param connectionMode Specifies the type of connection (standalone or networked)
   * @see JOptionPane
   */
  public DatabaseLocationDialog(Frame parent, ApplicationMode connectionMode) {
    configOptions = (new ConfigOptions(connectionMode));
    configOptions.getObservable().addObserver(this);

    // load saved configuration
    SavedConfiguration config = SavedConfiguration.getSavedConfiguration();

    // the port and connection type are irrelevant in standalone mode
    if (connectionMode == ApplicationMode.STANDALONE_CLIENT) {
      validPort = true;
      validCnx = true;
      networkType = ConnectionType.DIRECT;
      location = config.getParameter(SavedConfiguration.DATABASE_LOCATION);
    } else {
      // there may not be a network connectivity type defined and, if
      // not, we do not set a default - force the user to make a choice
      // the at least for the first time they run this.
      String tmp = config.getParameter(SavedConfiguration.NETWORK_TYPE);
      if (tmp != null) {
        try {
          networkType = ConnectionType.valueOf(tmp);
          configOptions.setNetworkConnection(networkType);
          validCnx = true;
        } catch (IllegalArgumentException e) {
          log.warning("Unknown connection type: " + networkType);
        }
      }

      // there is always at least a default port number, so we don't have
      // to validate this.
      port = config.getParameter(SavedConfiguration.SERVER_PORT);
      configOptions.setPortNumberText(port);
      validPort = true;

      location = config.getParameter(SavedConfiguration.SERVER_ADDRESS);
    }

    // there may not be a default database location, so we had better
    // validate before using the returned value.
    if (location != null) {
      configOptions.setLocationFieldText(location);
      validDb = true;
    }

    options =
        new JOptionPane(configOptions, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);

    connectButton.setActionCommand(CONNECT);
    connectButton.addActionListener(this);

    boolean allValid = validDb && validPort && validCnx;
    connectButton.setEnabled(allValid);

    exitButton.setActionCommand(EXIT);
    exitButton.addActionListener(this);

    options.setOptions(new Object[] {connectButton, exitButton});

    dialog = options.createDialog(parent, TITLE);
    dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
    dialog.addWindowListener(this);
    dialog.setVisible(true);
  }

  // Note: we can get away with not specifying the parameters in these
  // callback methods, as they are specified in the interfaces we are
  // implementing.

  /**
   * Callback event handler to process situations where the user has closed the window rather than
   * clicking one of the buttons.
   */
  public void windowClosing(WindowEvent we) {
    processCommand(EXIT);
  }

  /** Callback event handler to process clicks on any of the buttons. */
  public void actionPerformed(ActionEvent ae) {
    processCommand(ae.getActionCommand());
  }

  /**
   * Common event handling code - can handle desirable actions (such as buttons being clicked) and
   * undesirable actions (the window being closed) all in a common location.
   *
   * @param command a String representing the action that occurred.
   */
  private void processCommand(String command) {
    dialog.setVisible(false);
    if (CONNECT.equals(command)) {
      options.setValue(JOptionPane.OK_OPTION);
    } else {
      options.setValue(JOptionPane.CANCEL_OPTION);
    }
  }

  /**
   * Callback method to process modifications in the common ConfigOptions panel. ConfigOptions was
   * developed to be common to many applications (even though we only have three modes), so it does
   * not have any knowledge of how we are using it within this dialog box. So ConfigOptions just
   * sends updates to registered Observers whenever anything changes. We can receive those
   * notifications here, and decide whether we have enough information to enable the "Connect"
   * button of the dialog box.
   */
  public void update(Observable o, Object arg) {
    // we are going to ignore the Observable object, since we are only
    // observing one object. All we are interested in is the argument.

    if (!(arg instanceof OptionUpdate)) {
      log.log(
          Level.WARNING,
          "DatabaseLocationDialog received update type: " + arg,
          new IllegalArgumentException());
      return;
    }

    OptionUpdate optionUpdate = (OptionUpdate) arg;

    // load saved configuration
    SavedConfiguration config = SavedConfiguration.getSavedConfiguration();

    switch (optionUpdate.getUpdateType()) {
      case DB_LOCATION_CHANGED:
        location = (String) optionUpdate.getPayload();
        if (configOptions.getApplicationMode() == ApplicationMode.STANDALONE_CLIENT) {
          File f = new File(location);
          if (f.exists() && f.canRead() && f.canWrite()) {
            validDb = true;
            log.info("File chosen " + location);
            config.setParameter(SavedConfiguration.DATABASE_LOCATION, location);
          } else {
            log.warning("Invalid file " + location);
          }
        } else {
          try {
            if (location.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
              // location given matches 4 '.' separated numbers
              // regex could be improved by limiting each quad to
              // no more than 3 digits.
              String[] quads = location.split("\\.");
              byte[] address = new byte[quads.length];
              for (int i = 0; i < quads.length; i++) {
                address[i] = new Integer(quads[i]).byteValue();
              }
              InetAddress.getByAddress(address);
            } else {
              InetAddress.getAllByName(location);
            }
            log.info("Server specified " + location);
            validDb = true;
            config.setParameter(SavedConfiguration.SERVER_ADDRESS, location);
          } catch (UnknownHostException uhe) {
            log.warning("Unknown host: " + location);
            validDb = false;
          }
        }
        break;
      case PORT_CHANGED:
        port = (String) optionUpdate.getPayload();
        int p = Integer.parseInt(port);

        if (p >= LOWEST_PORT && p < HIGHEST_PORT) {
          if (p < SYSTEM_PORT_BOUNDARY) {
            log.info("User chose System port " + port);
          } else if (p < IANA_PORT_BOUNDARY) {
            log.info("User chose IANA port " + port);
          } else {
            log.info("User chose dynamic port " + port);
          }
          validPort = true;
          config.setParameter(SavedConfiguration.SERVER_PORT, port);
        } else {
          validPort = false;
        }
        break;
      case NETWORK_CHOICE_MADE:
        networkType = (ConnectionType) optionUpdate.getPayload();
        switch (networkType) {
          case SOCKET:
            log.info("Server connection via Sockets");
            break;
          case RMI:
            log.info("Server connection via RMI");
            break;
          default:
            log.info("Unknown connection type: " + networkType);
            break;
        }
        config.setParameter(SavedConfiguration.NETWORK_TYPE, "" + networkType);
        validCnx = true;
        break;
      default:
        log.warning("Unknown update: " + optionUpdate);
        break;
    }

    boolean allValid = validDb && validPort && validCnx;
    connectButton.setEnabled(allValid);
  }

  /**
   * Returns the location of the database, which may be either the path to the local database, or
   * the address of the network server hosting the database.
   *
   * @return the location of the database.
   */
  public String getLocation() {
    return location;
  }

  /**
   * Returns the port number the network server should be listening on for client connections.
   *
   * @return the port number for connecting to the network server.
   */
  public String getPort() {
    return port;
  }

  /**
   * Returns the type of network connection (Sockets, RMI ...) that should be used to connect to the
   * server.
   *
   * @return the network protocol used to connect to the server.
   */
  public ConnectionType getNetworkType() {
    return networkType;
  }

  /**
   * Let the caller of this dialog know whether the user connected or cancelled.
   *
   * @return true if the user cancelled or closed the window.
   */
  public boolean userCanceled() {
    if (options.getValue() instanceof Integer) {
      int status = ((Integer) options.getValue()).intValue();
      return status != JOptionPane.OK_OPTION;
    } else {
      return false;
    }
  }
}
/**
 * @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();
  }
}