예제 #1
0
  /** Initializes this component. */
  protected void init() {
    this.setLayout(new FlowLayout(FlowLayout.LEFT, 3, 0));
    this.setOpaque(false);

    this.add(inviteButton);

    // if we leave a chat room when we close the window
    // there is no need for this button
    if (!ConfigurationUtils.isLeaveChatRoomOnWindowCloseEnabled()) this.add(leaveChatRoomButton);

    this.add(callButton);
    this.add(callVideoButton);
    this.add(desktopSharingButton);
    this.add(sendFileButton);

    ChatPanel chatPanel = chatContainer.getCurrentChat();
    if (chatPanel == null || !(chatPanel.getChatSession() instanceof MetaContactChatSession))
      sendFileButton.setEnabled(false);

    this.addSeparator();

    this.add(historyButton);
    this.add(previousButton);
    this.add(nextButton);

    // We only add the options button if the property SHOW_OPTIONS_WINDOW
    // specifies so or if it's not set.
    Boolean showOptionsProp =
        GuiActivator.getConfigurationService()
            .getBoolean(ConfigurationFrame.SHOW_OPTIONS_WINDOW_PROPERTY, false);

    if (showOptionsProp.booleanValue()) {
      this.add(optionsButton);
    }

    this.addSeparator();

    if (ConfigurationUtils.isFontSupportEnabled()) {
      this.add(fontButton);
      fontButton.setName("font");
      fontButton.setToolTipText(
          GuiActivator.getResources().getI18NString("service.gui.CHANGE_FONT"));
      fontButton.addActionListener(this);
    }

    initSmiliesSelectorBox();

    this.addSeparator();

    this.inviteButton.setName("invite");
    this.inviteButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.INVITE"));

    this.leaveChatRoomButton.setName("leave");
    this.leaveChatRoomButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.LEAVE"));

    this.callButton.setName("call");
    this.callButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.CALL_CONTACT"));

    this.callVideoButton.setName("callVideo");
    this.callVideoButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.CALL_CONTACT"));

    this.desktopSharingButton.setName("desktop");
    this.desktopSharingButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SHARE_DESKTOP_WITH_CONTACT"));

    this.historyButton.setName("history");
    this.historyButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.HISTORY") + " Ctrl-H");

    optionsButton.setName("options");
    optionsButton.setToolTipText(GuiActivator.getResources().getI18NString("service.gui.OPTIONS"));

    this.sendFileButton.setName("sendFile");
    this.sendFileButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.SEND_FILE"));

    this.previousButton.setName("previous");
    this.previousButton.setToolTipText(
        GuiActivator.getResources().getI18NString("service.gui.PREVIOUS"));

    this.nextButton.setName("next");
    this.nextButton.setToolTipText(GuiActivator.getResources().getI18NString("service.gui.NEXT"));

    inviteButton.addActionListener(this);
    leaveChatRoomButton.addActionListener(this);
    callButton.addActionListener(this);
    callVideoButton.addActionListener(this);
    desktopSharingButton.addActionListener(this);
    historyButton.addActionListener(this);
    optionsButton.addActionListener(this);
    sendFileButton.addActionListener(this);
    previousButton.addActionListener(this);
    nextButton.addActionListener(this);
  }
예제 #2
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;
  }
}
예제 #3
0
/**
 * The button that starts/stops the call recording.
 *
 * @author Dmitri Melnikov
 * @author Lubomir Marinov
 */
public class RecordButton extends AbstractCallToggleButton {
  /** The logger used by the <tt>RecordButton</tt> class and its instances for logging output. */
  private static final Logger logger = Logger.getLogger(RecordButton.class);

  /** The date format used in file names. */
  private static final SimpleDateFormat FORMAT = new SimpleDateFormat("*****@*****.**");

  /** Configuration service. */
  private static final ConfigurationService configuration = GuiActivator.getConfigurationService();

  /** Resource service. */
  private static final ResourceManagementService resources = GuiActivator.getResources();

  /** Maximum allowed file name length. */
  private static final int MAX_FILENAME_LENGTH = 64;

  /** The full filename of the saved call on the file system. */
  private String callFilename;

  /** Call file chooser. */
  private SipCommFileChooser callFileChooser;

  /**
   * The <tt>Recorder</tt> which is depicted by this <tt>RecordButton</tt> and which is to record or
   * records {@link #call} into {@link #callFilename}.
   */
  private Recorder recorder;

  /**
   * Initializes a new <tt>RecordButton</tt> instance which is to record the audio stream.
   *
   * @param call the <tt>Call</tt> to be associated with the new instance and to have the audio
   *     stream recorded
   */
  public RecordButton(Call call) {
    this(call, false);
  }

  /**
   * Initializes a new <tt>RecordButton</tt> instance which is to record the audio stream.
   *
   * @param call the <tt>Call</tt> to be associated with the new instance and to have its audio
   *     stream recorded
   * @param selected <tt>true</tt> if the new toggle button is to be initially selected; otherwise,
   *     <tt>false</tt>
   */
  public RecordButton(Call call, boolean selected) {
    super(call, true, selected, ImageLoader.RECORD_BUTTON, ImageLoader.RECORD_BUTTON_PRESSED, null);

    String toolTip = resources.getI18NString("service.gui.RECORD_BUTTON_TOOL_TIP");
    String saveDir = configuration.getString(Recorder.SAVED_CALLS_PATH);

    if ((saveDir != null) && (saveDir.length() != 0)) toolTip += " (" + saveDir + ")";
    setToolTipText(toolTip);
  }

  /** Starts/stops the recording of the call when this button is pressed. */
  @Override
  public void buttonPressed() {
    if (call != null) {
      // start recording
      if (isSelected()) {
        boolean startedRecording = false;

        try {
          startedRecording = startRecording();
        } finally {
          if (!startedRecording && (recorder != null)) {
            try {
              recorder.stop();
            } finally {
              recorder = null;
            }
          }
          setSelected(startedRecording);
        }
      }
      // stop recording
      else if (recorder != null) {
        try {
          recorder.stop();
        } finally {
          recorder = null;
          setSelected(false);
        }
      }
    }
  }

  /**
   * Creates a full filename for the call by combining the directory, file prefix and extension. If
   * the directory is <tt>null</tt> user's home directory is used.
   *
   * @param savedCallsPath the path to the directory in which the generated file name is to be
   *     placed
   * @return a full filename for the call
   */
  private String createDefaultFilename(String savedCallsPath) {
    // set to user's home when null
    if (savedCallsPath == null) {
      try {
        savedCallsPath =
            GuiActivator.getFileAccessService().getDefaultDownloadDirectory().getAbsolutePath();
      } catch (IOException ioex) {
        // Leave it in the current directory.
      }
    }

    String ext = configuration.getString(Recorder.FORMAT);

    // Use a default format when the configured one seems invalid.
    if ((ext == null) || (ext.length() == 0) || !isSupportedFormat(ext))
      ext = SoundFileUtils.DEFAULT_CALL_RECORDING_FORMAT;
    return ((savedCallsPath == null) ? "" : (savedCallsPath + File.separator))
        + generateCallFilename(ext);
  }

  /**
   * Generates a file name for the call based on the current date and the names of the peers in the
   * call.
   *
   * @param ext file extension
   * @return the file name for the call
   */
  private String generateCallFilename(String ext) {
    String filename = FORMAT.format(new Date()) + "-call";
    int maxLength = MAX_FILENAME_LENGTH - 2 - filename.length() - ext.length();
    String peerName = getCallPeerName(maxLength);
    filename += ((!peerName.equals("")) ? "-" : "") + peerName + "." + ext;
    return filename;
  }

  /**
   * Gets and formats the names of the peers in the call.
   *
   * @param maxLength maximum length of the filename
   * @return the name of the peer in the call formated
   */
  private String getCallPeerName(int maxLength) {
    List<CallPeer> callPeers = call.getConference().getCallPeers();
    CallPeer callPeer = null;
    String peerName = "";
    if (!callPeers.isEmpty()) {
      callPeer = callPeers.get(0);
      if (callPeer != null) {
        peerName = callPeer.getDisplayName();
        peerName = peerName.replaceAll("[^\\da-zA-Z\\_\\-@\\.]", "");
        if (peerName.length() > maxLength) {
          peerName = peerName.substring(0, maxLength);
        }
      }
    }
    return peerName;
  }

  /**
   * Gets the <tt>Recorder</tt> represented by this <tt>RecordButton</tt> creating it first if it
   * does not exist.
   *
   * @return the <tt>Recorder</tt> represented by this <tt>RecordButton</tt> created first if it
   *     does not exist
   * @throws OperationFailedException if anything goes wrong while creating the <tt>Recorder</tt> to
   *     be represented by this <tt>RecordButton</tt>
   */
  private Recorder getRecorder() throws OperationFailedException {
    if (recorder == null) {
      OperationSetBasicTelephony<?> telephony =
          call.getProtocolProvider().getOperationSet(OperationSetBasicTelephony.class);

      recorder = telephony.createRecorder(call);
    }
    return recorder;
  }

  /**
   * Determines whether the extension of a specific <tt>File</tt> specifies a format supported by
   * the <tt>Recorder</tt> represented by this <tt>RecordButton</tt>.
   *
   * @param file the <tt>File</tt> whose extension is to be checked whether it specifies a format
   *     supported by the <tt>Recorder</tt> represented by this <tt>RecordButton</tt>
   * @return <tt>true</tt> if the extension of the specified <tt>file</tt> specifies a format
   *     supported by the <tt>Recorder</tt> represented by this <tt>RecordButton</tt>; otherwise,
   *     <tt>false</tt>
   */
  private boolean isSupportedFormat(File file) {
    String extension = SoundFileUtils.getExtension(file);

    return (extension != null) && (extension.length() != 0) && isSupportedFormat(extension);
  }

  /**
   * Determines whether a specific format is supported by the <tt>Recorder</tt> represented by this
   * <tt>RecordButton</tt>.
   *
   * @param format the format which is to be checked whether it is supported by the
   *     <tt>Recorder</tt> represented by this <tt>RecordButton</tt>
   * @return <tt>true</tt> if the specified <tt>format</tt> is supported by the <tt>Recorder</tt>
   *     represented by this <tt>RecordButton</tt>; otherwise, <tt>false</tt>
   */
  private boolean isSupportedFormat(String format) {
    Recorder recorder;

    try {
      recorder = getRecorder();
    } catch (OperationFailedException ofex) {
      logger.error("Failed to get Recorder", ofex);
      return false;
    }

    List<String> supportedFormats = recorder.getSupportedFormats();

    return (supportedFormats != null) && supportedFormats.contains(format);
  }

  /**
   * Starts recording {@link #call} creating {@link #recorder} first and asking the user for the
   * recording format and file if they are not configured in the "Call Recording" configuration
   * form.
   *
   * @return <tt>true</tt> if the recording has been started successfully; otherwise, <tt>false</tt>
   */
  private boolean startRecording() {
    String savedCallsPath = configuration.getString(Recorder.SAVED_CALLS_PATH);
    String callFormat;

    // Ask the user where to save the call.
    if ((savedCallsPath == null) || (savedCallsPath.length() == 0)) {
      /*
       * Delay the initialization of callFileChooser in order to delay the
       * creation of the recorder.
       */
      if (callFileChooser == null) {
        callFileChooser =
            GenericFileDialog.create(
                null,
                resources.getI18NString("plugin.callrecordingconfig.SAVE_CALL"),
                SipCommFileChooser.SAVE_FILE_OPERATION);
        callFileChooser.addFilter(
            new SipCommFileFilter() {
              @Override
              public boolean accept(File f) {
                return f.isDirectory() || isSupportedFormat(f);
              }

              @Override
              public String getDescription() {
                StringBuilder description = new StringBuilder();

                description.append("Recorded call");

                Recorder recorder;

                try {
                  recorder = getRecorder();
                } catch (OperationFailedException ofex) {
                  logger.error("Failed to get Recorder", ofex);
                  recorder = null;
                }
                if (recorder != null) {
                  List<String> supportedFormats = recorder.getSupportedFormats();

                  if (supportedFormats != null) {
                    description.append(" (");

                    boolean firstSupportedFormat = true;

                    for (String supportedFormat : supportedFormats) {
                      if (firstSupportedFormat) firstSupportedFormat = false;
                      else description.append(", ");
                      description.append("*.");
                      description.append(supportedFormat);
                    }

                    description.append(')');
                  }
                }
                return description.toString();
              }
            });
      }
      // Offer a default name for the file to record into.
      callFileChooser.setStartPath(createDefaultFilename(null));

      File selectedFile = callFileChooser.getFileFromDialog();

      if (selectedFile != null) {
        callFilename = selectedFile.getAbsolutePath();

        /*
         * If the user specified no extension (which seems common on Mac
         * OS X at least) i.e. no format, then it is not obvious that we
         * have to override the set Recorder.CALL_FORMAT.
         */
        callFormat = SoundFileUtils.getExtension(selectedFile);

        if ((callFormat != null) && (callFormat.length() != 0)) {
          /*
           * If the use has specified an extension and thus a format
           * which is not supported, use a default format instead.
           */
          if (!isSupportedFormat(selectedFile)) {
            /*
             * If what appears to be an extension seems a lot like
             * an extension, then it should be somewhat safer to
             * replace it.
             */
            if (SoundFileUtils.isSoundFile(selectedFile)) {
              callFilename = callFilename.substring(0, callFilename.lastIndexOf('.'));
            }
            String configuredFormat = configuration.getString(Recorder.FORMAT);
            callFormat =
                (configuredFormat != null && configuredFormat.length() != 0)
                    ? configuredFormat
                    : SoundFileUtils.DEFAULT_CALL_RECORDING_FORMAT;

            callFilename += '.' + callFormat;
          }
          configuration.setProperty(Recorder.FORMAT, callFormat);
        }
      } else {
        // user canceled the recording
        return false;
      }
    } else {
      callFilename = createDefaultFilename(savedCallsPath);
      callFormat = SoundFileUtils.getExtension(new File(callFilename));
    }

    Throwable exception = null;

    try {
      Recorder recorder = getRecorder();

      if (recorder != null) {
        if ((callFormat == null) || (callFormat.length() <= 0))
          callFormat = SoundFileUtils.DEFAULT_CALL_RECORDING_FORMAT;

        recorder.start(callFormat, callFilename);
      }

      this.recorder = recorder;
    } catch (IOException ioex) {
      exception = ioex;
    } catch (MediaException mex) {
      exception = mex;
    } catch (OperationFailedException ofex) {
      exception = ofex;
    }
    if ((recorder == null) || (exception != null)) {
      logger.error(
          "Failed to start recording call " + call + " into file " + callFilename, exception);
      return false;
    } else return true;
  }
}