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