/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.gui.main.chat.filetransfer; import java.awt.*; import java.awt.event.*; import java.io.*; import javax.swing.*; import net.java.sip.communicator.impl.gui.*; import net.java.sip.communicator.impl.gui.main.chat.*; import net.java.sip.communicator.impl.gui.utils.*; import net.java.sip.communicator.plugin.desktoputil.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; import net.java.sip.communicator.util.skin.*; /** * 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 final static 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 */ @Override 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() { @Override 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(); String bytesString = ByteFormat.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") + ByteFormat.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(); String text = ByteFormat.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) { String text = ByteFormat.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. */ @Override 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"))); } }