/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation. * * PowerFolder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.ui; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_CONNECTED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_LOGGED_IN; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NOT_STARTED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.NO_FOLDERS; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.PAUSED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNCHRONIZED; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNCING; import static de.dal33t.powerfolder.ui.event.SyncStatusEvent.SYNC_INCOMPLETE; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Frame; import java.awt.HeadlessException; import java.awt.Point; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowFocusListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JSplitPane; import javax.swing.SwingConstants; import javax.swing.TransferHandler; import javax.swing.WindowConstants; import javax.swing.event.ChangeListener; import javax.swing.plaf.RootPaneUI; import com.jgoodies.forms.builder.ButtonBarBuilder; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.builder.PanelBuilder; import com.jgoodies.forms.factories.Borders; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Constants; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.PreferencesEntry; import de.dal33t.powerfolder.clientserver.ServerClient; import de.dal33t.powerfolder.clientserver.ServerClientEvent; import de.dal33t.powerfolder.clientserver.ServerClientListener; import de.dal33t.powerfolder.event.FolderRepositoryEvent; import de.dal33t.powerfolder.event.FolderRepositoryListener; import de.dal33t.powerfolder.event.PausedModeEvent; import de.dal33t.powerfolder.event.PausedModeListener; import de.dal33t.powerfolder.message.clientserver.AccountDetails; import de.dal33t.powerfolder.security.ChangePreferencesPermission; import de.dal33t.powerfolder.security.FolderCreatePermission; import de.dal33t.powerfolder.security.OnlineStorageSubscription; import de.dal33t.powerfolder.ui.action.BaseAction; import de.dal33t.powerfolder.ui.dialog.DialogFactory; import de.dal33t.powerfolder.ui.dialog.GenericDialogType; import de.dal33t.powerfolder.ui.event.SyncStatusEvent; import de.dal33t.powerfolder.ui.event.SyncStatusListener; import de.dal33t.powerfolder.ui.model.FolderRepositoryModel; import de.dal33t.powerfolder.ui.notices.WarningNotice; import de.dal33t.powerfolder.ui.util.DelayedUpdater; import de.dal33t.powerfolder.ui.util.Icons; import de.dal33t.powerfolder.ui.util.NeverAskAgainResponse; import de.dal33t.powerfolder.ui.util.SyncIconButtonMini; import de.dal33t.powerfolder.ui.util.UIUtil; import de.dal33t.powerfolder.ui.widget.ActionLabel; import de.dal33t.powerfolder.ui.widget.JButton3Icons; import de.dal33t.powerfolder.ui.widget.JButtonMini; import de.dal33t.powerfolder.ui.wizard.PFWizard; import de.dal33t.powerfolder.util.BrowserLauncher; import de.dal33t.powerfolder.util.DateUtil; import de.dal33t.powerfolder.util.FileUtils; import de.dal33t.powerfolder.util.Format; import de.dal33t.powerfolder.util.StringUtils; import de.dal33t.powerfolder.util.Translation; import de.dal33t.powerfolder.util.os.OSUtil; import de.javasoft.plaf.synthetica.SyntheticaRootPaneUI; /** * Powerfolder gui mainframe * * @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a> * @version $Revision: 1.44 $ */ public class MainFrame extends PFUIComponent { private enum FrameMode {MAXIMIZED, NORMAL, COMPACT, MINIMIZED} public static final int MIN_MAIN_TABBED_WIDTH = 300; private JFrame uiComponent; private JLabel logoLabel; private JPanel centralPanel; private MainTabbedPane mainTabbedPane; private JPanel inlineInfoPanel; private JLabel inlineInfoLabel; private JButton inlineInfoCloseButton; private JSplitPane split; private ServerClient client; // Left mini panel private JButtonMini allInSyncButton; private SyncIconButtonMini syncingButton; private JButtonMini setupButton; private JButtonMini pauseButton; private JButtonMini syncIncompleteButton; private JLabel notConnectedLoggedInLabel; private ActionLabel upperMainTextActionLabel; private ActionLabel lowerMainTextActionLabel; private ActionLabel setupLabel; private ActionLabel loginActionLabel; private JProgressBar usagePB; private ActionLabel noticesActionLabel; private DelayedUpdater mainStatusUpdater; private DelayedUpdater osStatusUpdater; // Right mini panel private ActionLabel expandCollapseActionLabel; private MyExpandCollapseAction expandCollapseAction; private ActionLabel openWebInterfaceActionLabel; private ActionLabel openFoldersBaseActionLabel; private ActionLabel pauseResumeActionLabel; private ActionLabel configurationActionLabel; private ActionLabel openDebugActionLabel; private ActionLabel openTransfersActionLabel; private FrameMode frameMode = FrameMode.NORMAL; private JButton3Icons closeButton; private JButton3Icons plusButton; private JButton3Icons minusButton; /** Has the main frame state been set after init? */ private final AtomicBoolean frameStateSet = new AtomicBoolean(); /** * @param controller * the controller. * @throws HeadlessException */ public MainFrame(Controller controller) throws HeadlessException { super(controller); mainStatusUpdater = new DelayedUpdater(getController()); osStatusUpdater = new DelayedUpdater(getController()); controller.getFolderRepository().addFolderRepositoryListener( new MyFolderRepositoryListener()); initComponents(); configureUi(); updateOnlineStorageDetails(); // Start COMPACT for basic users, and NORMAL for experts. if (PreferencesEntry.EXPERT_MODE.getValueBoolean(getController())) { frameMode = FrameMode.NORMAL; } else { frameMode = FrameMode.COMPACT; } setFrameMode(frameMode, true); } private JPanel createMiniPanel() { FormLayout layout = new FormLayout("left:pref:grow, left:pref", "top:pref:grow"); DefaultFormBuilder builder = new DefaultFormBuilder(layout); builder.setBorder(Borders.createEmptyBorder("10dlu, 0, 0, 3dlu")); CellConstraints cc = new CellConstraints(); builder.add(createLeftMiniPanel(), cc.xy(1, 1)); builder.add(createRightMiniPanel(), cc.xy(2, 1)); return builder.getPanel(); } private Component createLeftMiniPanel() { CellConstraints cc = new CellConstraints(); // UPPER PART FormLayout layoutUpper = new FormLayout("pref, 3dlu, pref:grow", "pref, pref"); DefaultFormBuilder builderUpper = new DefaultFormBuilder(layoutUpper); PanelBuilder b = new PanelBuilder(new FormLayout("pref:grow", "pref:grow")); b.add(allInSyncButton, cc.xy(1, 1)); b.add(syncingButton, cc.xy(1, 1)); b.add(setupButton, cc.xy(1, 1)); b.add(pauseButton, cc.xy(1, 1)); b.add(syncIncompleteButton, cc.xy(1, 1)); b.add(notConnectedLoggedInLabel, cc.xy(1, 1)); builderUpper.add(b.getPanel(), cc.xywh(1, 1, 1, 2)); builderUpper.add(upperMainTextActionLabel.getUIComponent(), cc.xy(3, 1)); builderUpper.add(lowerMainTextActionLabel.getUIComponent(), cc.xy(3, 2)); builderUpper.add(setupLabel.getUIComponent(), cc.xy(3, 2)); // UPPER PART END // LOWER PART FormLayout layoutLower = new FormLayout("pref, 100dlu", "pref, pref, pref"); DefaultFormBuilder builderLower = new DefaultFormBuilder(layoutLower); // Include a spacer icon that lines up the pair with builderUpper // when allInSyncLabel has null icon. builderLower.add(new JLabel((Icon) null), cc.xywh(1, 1, 1, 2)); builderLower.add(loginActionLabel.getUIComponent(), cc.xy(2, 1)); builderLower.add(usagePB, cc.xy(2, 2)); // Make sure the noticesActionLabel vertical space is maintained. builderLower.add(new JLabel(" "), cc.xy(1, 3)); builderLower.add(noticesActionLabel.getUIComponent(), cc.xy(2, 3)); // LOWER PART END // PUT TOGETHER FormLayout layoutMain = new FormLayout("pref", "pref, 5dlu, pref"); DefaultFormBuilder builderMain = new DefaultFormBuilder(layoutMain); builderMain.setBorder(Borders.createEmptyBorder("0, 5dlu, 5dlu, 0")); builderMain.add(builderUpper.getPanel(), cc.xy(1, 1)); builderMain.add(builderLower.getPanel(), cc.xy(1, 3)); // PUT TOGETHER END return builderMain.getPanel(); } private Component createRightMiniPanel() { FormLayout layout = new FormLayout("pref:grow", "pref, pref, pref, pref, pref, pref, pref"); DefaultFormBuilder builder = new DefaultFormBuilder(layout); CellConstraints cc = new CellConstraints(); builder.add(expandCollapseActionLabel.getUIComponent(), cc.xy(1, 1)); if (ConfigurationEntry.WEB_LOGIN_ALLOWED .getValueBoolean(getController())) { builder.add(openWebInterfaceActionLabel.getUIComponent(), cc.xy(1, 2)); } builder.add(openFoldersBaseActionLabel.getUIComponent(), cc.xy(1, 3)); builder.add(pauseResumeActionLabel.getUIComponent(), cc.xy(1, 4)); builder.add(configurationActionLabel.getUIComponent(), cc.xy(1, 5)); if (getController().isVerbose()) { builder.add(openDebugActionLabel.getUIComponent(), cc.xy(1, 6)); } if (PreferencesEntry.EXPERT_MODE.getValueBoolean(getController())) { builder.add(openTransfersActionLabel.getUIComponent(), cc.xy(1, 7)); } return builder.getPanel(); } private void configureUi() { // Display the title pane. uiComponent.getRootPane().putClientProperty( "Synthetica.titlePane.enabled", Boolean.FALSE); uiComponent.getRootPane().updateUI(); FormLayout layout = new FormLayout("fill:pref:grow, pref, 3dlu, pref", "pref, fill:0:grow, pref"); DefaultFormBuilder builder = new DefaultFormBuilder(layout); CellConstraints cc = new CellConstraints(); builder.add(logoLabel, cc.xyw(1, 1, 3)); ButtonBarBuilder b = new ButtonBarBuilder(); b.addFixed(minusButton); b.addFixed(plusButton); b.addFixed(closeButton); builder.add(b.getPanel(), cc.xywh(4, 1, 1, 1, "right, top")); builder.add(inlineInfoLabel, cc.xy(2, 1, CellConstraints.DEFAULT, CellConstraints.BOTTOM)); builder.add(inlineInfoCloseButton, cc.xy(4, 1, CellConstraints.DEFAULT, CellConstraints.BOTTOM)); builder.add(centralPanel, cc.xyw(1, 2, 4)); builder.add(createMiniPanel(), cc.xyw(1, 3, 4)); uiComponent.getContentPane().removeAll(); uiComponent.getContentPane().add(builder.getPanel()); uiComponent.setResizable(true); Controller c = getController(); // Pack elements and set to default size. uiComponent.pack(); uiComponent.setSize(uiComponent.getWidth(), UIConstants.MAIN_FRAME_DEFAULT_HEIGHT); configureInlineInfo(); updateMainStatus(NOT_STARTED); updateNoticesLabel(); } /** * Show notices link if there are notices available. */ private void updateNoticesLabel() { int unreadCount = (Integer) getController().getUIController() .getApplicationModel().getNoticesModel() .getUnreadNoticesCountVM().getValue(); if (unreadCount == 0) { noticesActionLabel.setVisible(false); } else if (unreadCount == 1) { noticesActionLabel.setVisible(true); noticesActionLabel.setText(Translation.getTranslation( "main_frame.unread_notices.single.text")); } else { noticesActionLabel.setVisible(true); noticesActionLabel.setText(Translation.getTranslation( "main_frame.unread_notices.plural.text", String.valueOf(unreadCount))); } } /** * Asks user about exit behavior of the program when the program is used for * the first time */ private void handleExitFirstRequest() { boolean askForQuitOnX = PreferencesEntry.ASK_FOR_QUIT_ON_X .getValueBoolean(getController()); if (askForQuitOnX) { // Prompt for personal message. String[] options = { Translation .getTranslation("dialog.ask_for_quit_on_x.Minimize_button"), Translation .getTranslation("dialog.ask_for_quit_on_x.Exit_button")}; NeverAskAgainResponse response = DialogFactory.genericDialog( getController(), Translation.getTranslation("dialog.ask_for_quit_on_x.title"), Translation.getTranslation("dialog.ask_for_quit_on_x.text"), options, 0, GenericDialogType.QUESTION, Translation.getTranslation("general.neverAskAgain")); if (response.getButtonIndex() == 1) { // == Exit PreferencesEntry.QUIT_ON_X.setValue(getController(), true); } else { PreferencesEntry.QUIT_ON_X.setValue(getController(), false); } if (response.isNeverAskAgain()) { // don't ask me again PreferencesEntry.ASK_FOR_QUIT_ON_X.setValue(getController(), false); } } } /** * Initializes all ui components */ private void initComponents() { uiComponent = new JFrame(); uiComponent.setTransferHandler(new MyTransferHandler()); uiComponent.addWindowFocusListener(new MyWindowFocusListener()); uiComponent.setIconImage(Icons.getImageById(Icons.SMALL_LOGO)); uiComponent.setBackground(Color.white); BaseAction mySetupAction = new MySetupAction(getController()); mySetupAction.allowWith(FolderCreatePermission.INSTANCE); MyOpenFoldersBaseAction myOpenFoldersBaseAction = new MyOpenFoldersBaseAction(getController()); allInSyncButton = new JButtonMini(myOpenFoldersBaseAction); allInSyncButton.setIcon(Icons.getIconById(Icons.SYNC_COMPLETE)); allInSyncButton.setText(null); syncIncompleteButton = new JButtonMini(myOpenFoldersBaseAction); syncIncompleteButton.setIcon(Icons.getIconById(Icons.SYNC_INCOMPLETE)); syncIncompleteButton.setText(null); pauseButton = new JButtonMini(new MyPauseResumeAction(getController())); pauseButton.setIcon(Icons.getIconById(Icons.PAUSE)); pauseButton.setText(null); syncingButton = new SyncIconButtonMini(getController()); syncingButton.addActionListener(myOpenFoldersBaseAction); syncingButton.setVisible(false); setupButton = new JButtonMini(mySetupAction); setupButton.setIcon(Icons.getIconById(Icons.ACTION_ARROW)); setupButton.setText(null); notConnectedLoggedInLabel = new JLabel(Icons.getIconById(Icons.WARNING)); upperMainTextActionLabel = new ActionLabel(getController(), new AbstractAction() { public void actionPerformed(ActionEvent e) { handleSyncTextClick(); } }); lowerMainTextActionLabel = new ActionLabel(getController(), new AbstractAction() { public void actionPerformed(ActionEvent e) { handleSyncTextClick(); } }); setupLabel = new ActionLabel(getController(), mySetupAction); loginActionLabel = new ActionLabel(getController(), new MyLoginAction( getController())); noticesActionLabel = new ActionLabel(getController(), new MyShowNoticesAction(getController())); updateNoticesLabel(); usagePB = new JProgressBar(); usagePB.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); usagePB.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { try { BrowserLauncher.openURL(client.getWebURL( Constants.MY_ACCOUNT_URI, true)); } catch (IOException ex) { logSevere(ex); } } } }); expandCollapseAction = new MyExpandCollapseAction(getController()); expandCollapseActionLabel = new ActionLabel(getController(), expandCollapseAction); openWebInterfaceActionLabel = new ActionLabel(getController(), new MyOpenWebInterfaceAction(getController())); openFoldersBaseActionLabel = new ActionLabel(getController(), myOpenFoldersBaseAction); pauseResumeActionLabel = new ActionLabel(getController(), new MyPauseResumeAction(getController())); configurationActionLabel = new ActionLabel(getController(), new MyOpenPreferencesAction(getController())); openDebugActionLabel = new ActionLabel(getController(), new MyOpenDebugAction(getController())); openTransfersActionLabel = new ActionLabel(getController(), new MyOpenTransfersAction(getController())); // add window listener, checks if exit is needed on pressing X MyWindowListener myWindowListener = new MyWindowListener(); uiComponent.addWindowListener(myWindowListener); uiComponent.addWindowStateListener(myWindowListener); split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); split.setOneTouchExpandable(false); // everything is decided in window listener uiComponent .setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); logoLabel = new JLabel(); logoLabel.setIcon(Icons.getIconById(Icons.LOGO400UI)); logoLabel.setHorizontalAlignment(SwingConstants.LEFT); logoLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); MyMouseWindowDragListener logoMouseListener = new MyMouseWindowDragListener(); logoLabel.addMouseListener(logoMouseListener); logoLabel.addMouseMotionListener(logoMouseListener); closeButton = new JButton3Icons( Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_NORMAL), Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_HOVER), Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_PUSH)); closeButton.setToolTipText(Translation .getTranslation("main_frame.close.tips")); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doCloseOperation(); } }); plusButton = new JButton3Icons( Icons.getIconById(Icons.WINDOW_PLUS_NORMAL), Icons.getIconById(Icons.WINDOW_PLUS_HOVER), Icons.getIconById(Icons.WINDOW_PLUS_PUSH)); plusButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doPlusOperation(); } }); minusButton = new JButton3Icons( Icons.getIconById(Icons.WINDOW_MINUS_NORMAL), Icons.getIconById(Icons.WINDOW_MINUS_HOVER), Icons.getIconById(Icons.WINDOW_MINUS_PUSH)); minusButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doMinusOperation(); } }); centralPanel = new JPanel(new BorderLayout(0, 0)); mainTabbedPane = new MainTabbedPane(getController()); updateTitle(); inlineInfoCloseButton = new JButton3Icons( Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_NORMAL), Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_HOVER), Icons.getIconById(Icons.FILTER_TEXT_FIELD_CLEAR_BUTTON_PUSH)); inlineInfoCloseButton.setToolTipText(Translation .getTranslation("main_frame.inline_info_close.tip")); inlineInfoCloseButton .addActionListener(new MyInlineCloseInfoActionListener()); inlineInfoCloseButton.setContentAreaFilled(false); inlineInfoLabel = new JLabel(); configurePauseResumeLink(); client = getApplicationModel().getServerClientModel().getClient(); client.addListener(new MyServerClientListener()); // Start listening to notice changes. getController().getUIController().getApplicationModel() .getNoticesModel().getAllNoticesCountVM() .addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { updateNoticesLabel(); } }); getController().getUIController().getApplicationModel() .getNoticesModel().getUnreadNoticesCountVM() .addValueChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { updateNoticesLabel(); } }); getController().getUIController().getApplicationModel().addSyncStatusListener(new SyncStatusListener() { public void syncStatusChanged(final SyncStatusEvent event) { mainStatusUpdater.schedule(new Runnable() { public void run() { updateMainStatus(event); } }); } public boolean fireInEventDispatchThread() { return true; } }); getController().addPausedModeListener(new MyPausedModeListener()); } private void handleSyncTextClick() { if (getController().isPaused()) { getController().setPaused(false); } else if (frameMode == FrameMode.COMPACT) { setFrameMode(FrameMode.NORMAL); } else { setFrameMode(FrameMode.COMPACT); } setLinkTooltips(); } private void updateMainStatus(SyncStatusEvent event) { FolderRepositoryModel folderRepositoryModel = getUIController() .getApplicationModel().getFolderRepositoryModel(); // Set visibility of buttons and labels. pauseButton.setVisible(event.equals(PAUSED)); setupLabel.setVisible(event.equals(NOT_STARTED) || event.equals(NO_FOLDERS)); setupButton.setVisible(event.equals(NOT_STARTED) || event.equals(NO_FOLDERS)); allInSyncButton.setVisible(event.equals(SYNCHRONIZED)); syncingButton.setVisible(event.equals(SYNCING)); syncingButton.spin(event.equals(SYNCING)); syncIncompleteButton.setVisible(event.equals(SYNC_INCOMPLETE)); notConnectedLoggedInLabel.setVisible(event.equals(NOT_CONNECTED) || event.equals(NOT_LOGGED_IN)); // Default sync date. Date syncDate = folderRepositoryModel.getLastSyncDate(); // Upper text / setup text stuff. double overallSyncPercentage = folderRepositoryModel .getOverallSyncPercentage(); String upperText = " "; String setupText = " "; if (event.equals(PAUSED)) { String pausedTemp = overallSyncPercentage >= 0 && overallSyncPercentage < 99.5d ? Format .formatPercent(overallSyncPercentage) : ""; upperText = Translation.getTranslation("main_frame.paused", pausedTemp); } else if (event.equals(NOT_STARTED)) { upperText = Translation.getTranslation("main_frame.not_running"); setupText = Translation.getTranslation("main_frame.activate_now"); } else if (event.equals(NO_FOLDERS)) { upperText = Translation.getTranslation("main_frame.no_folders"); setupText = getApplicationModel().getActionModel() .getNewFolderAction().getName(); } else if (event.equals(SYNCING)) { syncDate = folderRepositoryModel.getEstimatedSyncDate(); String syncingTemp = overallSyncPercentage >= 0 && overallSyncPercentage < 99.5d ? Format.formatDecimal(overallSyncPercentage) + '%' : "..."; upperText = Translation.getTranslation("main_frame.syncing", syncingTemp); } else if (event.equals(SYNCHRONIZED)) { upperText = Translation.getTranslation("main_frame.in_sync"); } else if (event.equals(SYNC_INCOMPLETE)) { upperText = Translation.getTranslation( "main_frame.sync_incomplete"); } else if (event.equals(NOT_CONNECTED)) { upperText = Translation.getTranslation("main_frame.connecting.text"); } else if (event.equals(NOT_LOGGED_IN)) { upperText = Translation.getTranslation("main_frame.logging_in.text"); } else { logSevere("Not handling all sync states: " + event); } upperMainTextActionLabel.setText(upperText); setupLabel.setText(setupText); // The lowerMainTextActionLabel and setupLabel share the same slot, // so visibility is mutually exclusive. boolean notStartedOrNoFolders = event.equals(NOT_STARTED) || event.equals(NO_FOLDERS); setupLabel.setVisible(notStartedOrNoFolders); lowerMainTextActionLabel.setVisible(!notStartedOrNoFolders); // Lower text - sync date stuff. String lowerText = " "; if (syncDate != null) { //If ETA sync > 3 days: no text. if (!DateUtil.isDateMoreThanNDaysInFuture(syncDate, 3)) { String date = Format.formatDateShort(syncDate); boolean inFuture = syncDate.after(new Date()); if (inFuture) { // If ETA sync > 20 hours show text: "Estimated sync: in X days" // If ETA sync > 45 minutes show text: "Estimated sync: in X hours" // If ETA sync < 45 minutes show text: "Estimated sync: in X minutes" if (DateUtil.isDateMoreThanNHoursInFuture(syncDate, 20)) { int days = DateUtil.getDaysInFuture(syncDate); if (days <= 1) { lowerText = Translation.getTranslation( "main_frame.sync_eta_one_day"); } else { lowerText = Translation.getTranslation( "main_frame.sync_eta_days", String.valueOf( days)); } } else if (DateUtil.isDateMoreThanNMinutesInFuture(syncDate, 45)) { int hours = DateUtil.getHoursInFuture(syncDate); if (hours <= 1) { lowerText = Translation.getTranslation( "main_frame.sync_eta_one_hour"); } else { lowerText = Translation.getTranslation( "main_frame.sync_eta_hours", String.valueOf( hours)); } } else { int minutes = DateUtil.getMinutesInFuture(syncDate); if (minutes <= 1) { lowerText = Translation.getTranslation( "main_frame.sync_eta_one_minute"); } else { lowerText = Translation.getTranslation( "main_frame.sync_eta_minutes", String.valueOf(minutes)); } } } else { lowerText = Translation.getTranslation( "main_frame.last_synced", date); } } } lowerMainTextActionLabel.setText(lowerText); } /** * Updates the title */ public void updateTitle() { StringBuilder title = new StringBuilder(); String appName = Translation.getTranslation("general.application.name"); // @todo Unclear, please comment what this is about. if (StringUtils.isEmpty(appName) || appName.startsWith("- ")) { appName = "PowerFolder"; } title.append(appName); if (getController().isVerbose()) { // Append in front of program name in verbose mode title.append(" v" + Controller.PROGRAM_VERSION); if (getController().getBuildTime() != null) { title.append(" | build: " + getController().getBuildTime()); } title.append(" | " + getController().getMySelf().getNick()); } Calendar cal = Calendar.getInstance(); cal.setTime(new Date()); if (cal.get(Calendar.DAY_OF_MONTH) == 21 && cal.get(Calendar.MONTH) == 2) { title.append(" | Happy birthday archi !"); } uiComponent.setTitle(title.toString()); } /** * @return the ui panel of the mainframe. */ public JFrame getUIComponent() { return uiComponent; } /** * Add a change listener to the main tabbed pane selection. * * @param l */ public void addTabbedPaneChangeListener(ChangeListener l) { mainTabbedPane.addTabbedPaneChangeListener(l); } /** * Remove a change listener from the main tabbed pane. * * @param l */ public void removeTabbedPaneChangeListener(ChangeListener l) { mainTabbedPane.removeTabbedPaneChangeListener(l); } /** * Stores all current window values. */ public void storeValues() { // Store main window preferences Controller controller = getController(); if (isMaximized()) { PreferencesEntry.MAIN_FRAME_MAXIMIZED.setValue(controller, true); } else { PreferencesEntry.MAIN_FRAME_MAXIMIZED.setValue(controller, false); } } /** * @return true, if application is currently minimized */ public boolean isIconified() { return (uiComponent.getExtendedState() & Frame.ICONIFIED) != 0; } /** * @return true, if application is currently minimized */ public boolean isMaximized() { return (uiComponent.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0; } /** * Determine if application is currently minimized or hidden (for example, * in the systray) * * @return true, if application is currently minimized or hidden */ public boolean isIconifiedOrHidden() { return isIconified() || !uiComponent.isVisible(); } /** * For non-experts, if they log in * and only have online folders and no local folders, * and if the user has not already changed the frame state manually * then change from compact to normal, * helping the user to see the online folders * so they can do something about it. */ private void showOSFolderList() { if (frameStateSet.get()) { return; } if (PreferencesEntry.EXPERT_MODE.getValueBoolean(getController())) { return; } if (!getController().getFolderRepository().getFolders().isEmpty()) { return; } if (client.getAccountFolders().isEmpty()) { return; } if (frameMode == FrameMode.COMPACT) { setFrameMode(FrameMode.NORMAL); } } /** * @return the selected main tab index. */ public int getSelectedMainTabIndex() { return mainTabbedPane.getSelectedTabIndex(); } /** * Shows the folders tab. */ public void showFoldersTab() { mainTabbedPane.setActiveTab(MainTabbedPane.FOLDERS_INDEX); } public void showInlineInfoPanel(JPanel panel, String title) { // Fix Synthetica maximization, otherwise it covers the task bar. // See http://www.javasoft.de/jsf/public/products/synthetica/faq#q13 RootPaneUI ui = uiComponent.getRootPane().getUI(); if (ui instanceof SyntheticaRootPaneUI) { ((SyntheticaRootPaneUI) ui).setMaximizedBounds(uiComponent); } inlineInfoPanel = panel; inlineInfoLabel.setText(title); configureInlineInfo(); } private void closeInlineInfoPanel() { inlineInfoPanel = null; configureInlineInfo(); } public boolean isShowingInfoInline() { return inlineInfoPanel != null; } private void configureInlineInfo() { boolean displaying = isShowingInfoInline(); inlineInfoCloseButton.setVisible(displaying); if (displaying) { centralPanel.removeAll(); split.setLeftComponent(mainTabbedPane.getUIComponent()); split.setRightComponent(inlineInfoPanel); centralPanel.add(split, BorderLayout.CENTER); } else { // Split pane place holders split.setLeftComponent(new JPanel()); split.setRightComponent(new JPanel()); centralPanel.removeAll(); centralPanel.add(mainTabbedPane.getUIComponent(), BorderLayout.CENTER); inlineInfoPanel = null; inlineInfoLabel.setText(""); } if (frameMode == FrameMode.NORMAL) { configureNormalSize(); } } /** * Source: * http://stackoverflow.com/questions/309023/howto-bring-a-java-window * -to-the-front */ public void toFront() { uiComponent.setVisible(true); int state = uiComponent.getExtendedState(); state &= ~Frame.ICONIFIED; uiComponent.setExtendedState(state); uiComponent.toFront(); uiComponent.requestFocus(); } private void doCloseOperation() { if (OSUtil.isSystraySupported()) { if (PreferencesEntry.EXPERT_MODE.getValueBoolean(getController())) { handleExitFirstRequest(); } boolean quitOnX = PreferencesEntry.QUIT_ON_X .getValueBoolean(getController()); if (quitOnX) { exitProgram(); } else { getUIController().hideChildPanels(); uiComponent.setVisible(false); } } else { // Quit if systray is not Supported by OS. exitProgram(); } } private void doMinusOperation() { if (frameMode == FrameMode.MAXIMIZED || frameMode == FrameMode.NORMAL) { // To COMPACT mode. setFrameMode(FrameMode.COMPACT); } else { setFrameMode(FrameMode.MINIMIZED); } } private void doPlusOperation() { if (frameMode == FrameMode.MAXIMIZED) { // To NORMAL mode. setFrameMode(FrameMode.NORMAL); } else if (frameMode == FrameMode.NORMAL) { // To MAXIMIZED mode. setFrameMode(FrameMode.MAXIMIZED); } else { // To NORMAL mode. setFrameMode(FrameMode.NORMAL); } } /** * Shuts down the program */ private void exitProgram() { if (getUIController().isShutdownAllowed()) { uiComponent.setVisible(false); uiComponent.dispose(); new Thread("Close PowerFolder Thread") { @Override public void run() { getController().exit(0); } }.start(); } } // //////////////// // Inner Classes // // //////////////// private class MyWindowFocusListener implements WindowFocusListener { public void windowGainedFocus(WindowEvent e) { getUIController().setActiveFrame(UIController.MAIN_FRAME_ID); } public void windowLostFocus(WindowEvent e) { // Nothing to do here. } } private class MyWindowListener extends WindowAdapter { public void windowClosing(WindowEvent e) { doCloseOperation(); } /** * Hide other frames when main frame gets minimized. * * @param e */ public void windowIconified(WindowEvent e) { getUIController().hideChildPanels(); if (OSUtil.isSystraySupported()) { uiComponent.setVisible(false); } } } private class MyFolderRepositoryListener implements FolderRepositoryListener { // If showing the inline panel and the folder has been removed, // close the inline panel. public void folderRemoved(FolderRepositoryEvent e) { if (isShowingInfoInline()) { closeInlineInfoPanel(); } } public void folderCreated(FolderRepositoryEvent e) { // Let the MainTabbedPane know so it can scroll the Folders tab to the new folder. mainTabbedPane.folderCreated(e); } public void maintenanceStarted(FolderRepositoryEvent e) { // Don't care. } public void maintenanceFinished(FolderRepositoryEvent e) { // Don't care. } public boolean fireInEventDispatchThread() { return true; } } private void configurePauseResumeLink() { if (getController().isPaused()) { pauseResumeActionLabel.setText(Translation .getTranslation("action_resume_sync.name")); pauseResumeActionLabel.setToolTipText(Translation .getTranslation("action_resume_sync.description")); } else { pauseResumeActionLabel.setText(Translation .getTranslation("action_pause_sync.name")); pauseResumeActionLabel.setToolTipText(Translation .getTranslation("action_pause_sync.description")); } } private void checkCloudSpace() { if (PreferencesEntry.WARN_FULL_CLOUD.getValueBoolean(getController())) { if (client != null && client.isLoggedIn()) { if (!client.getAccount().getOSSubscription().isDisabled()) { long storageSize = client.getAccount().getOSSubscription() .getStorageSize(); long used = client.getAccountDetails().getSpaceUsed(); if (used >= storageSize * 9 / 10) { // More than 90% used. Notify. WarningNotice notice = new WarningNotice( Translation.getTranslation( "warning_notice.title"), Translation.getTranslation( "warning_notice.cloud_full_summary"), Translation.getTranslation( "warning_notice.cloud_full_message")); getUIController().getApplicationModel() .getNoticesModel().handleNotice(notice); } } } } } private void updateOnlineStorageDetails() { osStatusUpdater.schedule(new Runnable() { public void run() { updateOnlineStorageDetails0(); } }); } private void updateOnlineStorageDetails0() { double percentageUsed = 0; long totalStorage = 0; long spaceUsed = 0; if (StringUtils.isBlank(client.getUsername())) { loginActionLabel.setText(Translation .getTranslation("main_frame.account_not_set.text")); } else if (client.isPasswordEmpty()) { loginActionLabel.setText(Translation .getTranslation("main_frame.password_required.text")); } else if (client.isConnected()) { if (client.isLoggedIn()) { OnlineStorageSubscription storageSubscription = client .getAccount().getOSSubscription(); AccountDetails ad = client.getAccountDetails(); if (storageSubscription.isDisabled()) { loginActionLabel.setText(Translation .getTranslation("main_frame.storage_subscription_disabled.text")); } else { totalStorage = storageSubscription.getStorageSize(); spaceUsed = ad.getSpaceUsed(); if (totalStorage > 0) { percentageUsed = 100.0d * (double) spaceUsed / (double) totalStorage; } percentageUsed = Math.max(0.0d, percentageUsed); percentageUsed = Math.min(100.0d, percentageUsed); String s = client.getUsername(); if (!StringUtils.isEmpty(s)) { loginActionLabel.setText(s); } } } else if (client.isLoggingIn()) { loginActionLabel.setText(Translation .getTranslation("main_frame.logging_in.text")); } else { // Not logged in and not logging in? Looks like it has failed. loginActionLabel.setText(Translation .getTranslation("main_frame.log_in_failed.text")); } } else { loginActionLabel.setText(Translation .getTranslation("main_frame.connecting.text")); } usagePB.setValue((int) percentageUsed); usagePB.setToolTipText(Format.formatBytesShort(spaceUsed) + " / " + Format.formatBytesShort(totalStorage)); } private void setFrameMode(FrameMode frameMode) { // Remember that the frame has been set manually. frameStateSet.set(true); setFrameMode(frameMode, false); } private void setFrameMode(FrameMode frameMode, boolean init) { expandCollapseAction.setShowExpand(frameMode == FrameMode.COMPACT); this.frameMode = frameMode; switch (frameMode) { case MAXIMIZED : // http://www.javasoft.de/synthetica/faq/#general-7 if (uiComponent.getRootPane().getUI() instanceof SyntheticaRootPaneUI) { ((SyntheticaRootPaneUI) uiComponent.getRootPane().getUI()) .setMaximizedBounds(uiComponent); } uiComponent.setExtendedState(Frame.MAXIMIZED_BOTH); plusButton.setToolTipText(Translation .getTranslation("main_frame.restore.tips")); plusButton.setIcons( Icons.getIconById(Icons.WINDOW_PLUS_NORMAL), Icons.getIconById(Icons.WINDOW_PLUS_HOVER), Icons.getIconById(Icons.WINDOW_PLUS_PUSH)); minusButton.setVisible(true); minusButton.setToolTipText( Translation.getTranslation("main_frame.compact.tips")); checkSplitMinWidth(); break; case NORMAL: uiComponent.setExtendedState(Frame.NORMAL); uiComponent.setResizable(true); plusButton.setToolTipText( Translation.getTranslation("main_frame.maximize.tips")); plusButton.setIcons(Icons.getIconById( Icons.WINDOW_MAXIMIZE_NORMAL), Icons.getIconById(Icons.WINDOW_MAXIMIZE_HOVER), Icons.getIconById(Icons.WINDOW_MAXIMIZE_PUSH)); minusButton.setVisible(true); minusButton.setToolTipText( Translation.getTranslation("main_frame.compact.tips")); configureNormalSize(); UIUtil.invokeLaterInEDT(new Runnable() { public void run() { configureNormalSize(); } }); break; case COMPACT: uiComponent.setExtendedState(Frame.NORMAL); // Need to hide the child windows when minimizing. if (!init) { closeInlineInfoPanel(); getUIController().hideChildPanels(); } uiComponent.setSize(uiComponent.getMinimumSize()); uiComponent.setResizable(false); plusButton.setToolTipText( Translation.getTranslation("main_frame.restore.tips")); plusButton.setIcons(Icons.getIconById(Icons.WINDOW_PLUS_NORMAL), Icons.getIconById(Icons.WINDOW_PLUS_HOVER), Icons.getIconById(Icons.WINDOW_PLUS_PUSH)); minusButton.setToolTipText( Translation.getTranslation("main_frame.minimize.tips")); // Don't show minimize button if systray is available // and the exit button uses minimize option. minusButton.setVisible(!OSUtil.isSystraySupported() || PreferencesEntry.QUIT_ON_X.getValueBoolean(getController())); toFront(); UIUtil.invokeLaterInEDT(new Runnable() { public void run() { uiComponent.setSize(uiComponent.getMinimumSize()); } }); break; case MINIMIZED: uiComponent.setExtendedState(Frame.ICONIFIED); break; } setLinkTooltips(); } private void configureNormalSize() { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int w = isShowingInfoInline() ? Constants.DEFAULT_NORMAL_DOCKED_WIDTH : (int) uiComponent.getMinimumSize().getWidth(); int x = (int) ((screenSize.getWidth() - w) / 2.0); int h = Constants.DEFAULT_NORMAL_HEIGHT; int y = (int) ((screenSize.getHeight() - h) / 2.0); uiComponent.setSize(w, h); uiComponent.setLocation(x, y); checkSplitMinWidth(); } /** * If showing inline, check that the divider is not too wide. */ private void checkSplitMinWidth() { if (isShowingInfoInline()) { if (split.getDividerLocation() < MIN_MAIN_TABBED_WIDTH) { split.setDividerLocation(MIN_MAIN_TABBED_WIDTH); } // No clue why this have to be done later. // However if not this change does not come thru // on the first time the inline component/splitpane is shown. UIUtil.invokeLaterInEDT(new Runnable() { public void run() { if (split.getDividerLocation() < MIN_MAIN_TABBED_WIDTH) { split.setDividerLocation(MIN_MAIN_TABBED_WIDTH); } } }); } } private void setLinkTooltips() { if (getController().isPaused()) { upperMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_resume_sync.description")); lowerMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_resume_sync.description")); } else if (frameMode == FrameMode.COMPACT) { upperMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_expand_interface.name")); lowerMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_expand_interface.name")); } else { upperMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_collapse_interface.name")); lowerMainTextActionLabel.setToolTipText(Translation.getTranslation( "action_collapse_interface.name")); } } // //////////////// // Inner classes // // //////////////// private class MyInlineCloseInfoActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == inlineInfoCloseButton) { closeInlineInfoPanel(); } } } private class MyOpenWebInterfaceAction extends BaseAction { private MyOpenWebInterfaceAction(Controller controller) { super("action_open_web_interface", controller); } public void actionPerformed(ActionEvent e) { // PFC-2349 : Don't freeze UI getController().getIOProvider().startIO(new Runnable() { public void run() { try { BrowserLauncher.openURL(client .getLoginURLWithCredentials()); } catch (IOException e1) { logWarning("Unable to open web portal", e1); } } }); } } private class MyExpandCollapseAction extends BaseAction { private MyExpandCollapseAction(Controller controller) { super("action_expand_interface", controller); } public void actionPerformed(ActionEvent e) { if (frameMode == FrameMode.MAXIMIZED || frameMode == FrameMode.NORMAL) { setFrameMode(FrameMode.COMPACT); } else { setFrameMode(FrameMode.NORMAL); } } public void setShowExpand(boolean expand) { if (expand) { configureFromActionId("action_expand_interface"); } else { configureFromActionId("action_collapse_interface"); } } } private static class MyOpenFoldersBaseAction extends BaseAction { private MyOpenFoldersBaseAction(Controller controller) { super("action_open_folders_base", controller); } public void actionPerformed(ActionEvent e) { // PFC-2349 : Don't freeze UI getController().getIOProvider().startIO(new Runnable() { public void run() { FileUtils.openFile(getController().getFolderRepository() .getFoldersBasedir()); } }); } } private class MySetupAction extends BaseAction { protected MySetupAction(Controller controller) { super(null, controller); } public void actionPerformed(ActionEvent e) { if (!getController().getNodeManager().isStarted()) { getApplicationModel().getLicenseModel().getActivationAction() .actionPerformed(e); } else if (getController().getFolderRepository().getFoldersCount() == 0) { getApplicationModel().getActionModel().getNewFolderAction() .actionPerformed(e); } } } private class MyPauseResumeAction extends BaseAction { private MyPauseResumeAction(Controller controller) { super("action_resume_sync", controller); } public void actionPerformed(ActionEvent e) { getUIController().askToPauseResume(); setLinkTooltips(); } } private class MyOpenTransfersAction extends BaseAction { private MyOpenTransfersAction(Controller controller) { super("action_open_transfers_information", controller); } public void actionPerformed(ActionEvent e) { if (frameMode == FrameMode.COMPACT) { setFrameMode(FrameMode.NORMAL); } getUIController().openTransfersInformation(); } } private class MyOpenDebugAction extends BaseAction { private MyOpenDebugAction(Controller controller) { super("action_open_debug_information", controller); } public void actionPerformed(ActionEvent e) { if (frameMode == FrameMode.COMPACT) { setFrameMode(FrameMode.NORMAL); } getUIController().openDebugInformation(); } } private class MyServerClientListener implements ServerClientListener { public boolean fireInEventDispatchThread() { return true; } public void accountUpdated(ServerClientEvent event) { updateOnlineStorageDetails(); checkCloudSpace(); } public void login(ServerClientEvent event) { showOSFolderList(); updateOnlineStorageDetails(); checkCloudSpace(); } public void serverConnected(ServerClientEvent event) { updateOnlineStorageDetails(); } public void serverDisconnected(ServerClientEvent event) { updateOnlineStorageDetails(); } public void nodeServerStatusChanged(ServerClientEvent event) { updateOnlineStorageDetails(); } } private class MyLoginAction extends BaseAction { MyLoginAction(Controller controller) { super("action_login", controller); } public void actionPerformed(ActionEvent e) { PFWizard.openLoginWizard(getController(), client); } } private class MyShowNoticesAction extends BaseAction { MyShowNoticesAction(Controller controller) { super("action_show_notices", controller); } public void actionPerformed(ActionEvent e) { if (frameMode == FrameMode.COMPACT) { setFrameMode(FrameMode.NORMAL); } getController().getUIController().openNoticesCard(); } } private class MyMouseWindowDragListener extends MouseAdapter { private int startX; private int startY; private boolean inDrag; @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { if (isMaximized()) { setFrameMode(FrameMode.NORMAL); } else { setFrameMode(FrameMode.MAXIMIZED); } } } /** Called when the mouse has been pressed. */ public void mousePressed(MouseEvent e) { if (isMaximized()) { // No drag allowed if maximized return; } Point p = e.getPoint(); startX = p.x; startY = p.y; inDrag = true; } /** Called when the mouse has been released. */ public void mouseReleased(MouseEvent e) { if (isMaximized()) { // No drag allowed if maximized return; } inDrag = false; } // And two methods from MouseMotionListener: public void mouseDragged(MouseEvent e) { if (isMaximized()) { // No drag allowed if maximized return; } Point p = e.getPoint(); if (inDrag) { int dx = p.x - startX; int dy = p.y - startY; Point l = uiComponent.getLocation(); uiComponent.setLocation(l.x + dx, l.y + dy); } } } /** * Handle drag-n-drops of Folders direct into the application. */ @SuppressWarnings("serial") private class MyTransferHandler extends TransferHandler { public boolean canImport(TransferSupport support) { return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor); } @SuppressWarnings("unchecked") public boolean importData(TransferSupport support) { try { Transferable t = support.getTransferable(); List<File> fileList = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor); // One at a time! if (fileList == null || fileList.size() != 1) { logInfo("Skipping importData (multiple)."); return false; } // Directories only. File file = fileList.get(0); if (!file.isDirectory()) { logInfo("Skipping importData (not directory)."); return false; } // Does user have folder create permission? if (ConfigurationEntry.SECURITY_PERMISSIONS_STRICT .getValueBoolean(getController()) && !getController().getOSClient().getAccount() .hasPermission(FolderCreatePermission.INSTANCE)) { logInfo("Skipping importData (no folder create permission)."); return false; } // Make sure we do not already have this as a folder. if (getController().getFolderRepository() .doesFolderAlreadyExist(file)) { logInfo("Skipping importData (already have)."); return false; } // All good then... PFWizard.openExistingDirectoryWizard(getController(), file); } catch (UnsupportedFlavorException e) { logSevere(e); return false; } catch (IOException e) { logSevere(e); return false; } return true; } } private static class MyOpenPreferencesAction extends BaseAction { private MyOpenPreferencesAction(Controller controller) { super("action_open_preferences", controller); allowWith(ChangePreferencesPermission.INSTANCE); } public void actionPerformed(ActionEvent e) { getUIController().openPreferences(); } } private class MyPausedModeListener implements PausedModeListener { public void setPausedMode(PausedModeEvent event) { configurePauseResumeLink(); } public boolean fireInEventDispatchThread() { return true; } } }