/*
* 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 java.awt.EventQueue;
import java.awt.Menu;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.SwingUtilities;
import javax.swing.UnsupportedLookAndFeelException;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import de.dal33t.powerfolder.ConfigurationEntry;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.Feature;
import de.dal33t.powerfolder.Member;
import de.dal33t.powerfolder.PFComponent;
import de.dal33t.powerfolder.PreferencesEntry;
import de.dal33t.powerfolder.disk.Folder;
import de.dal33t.powerfolder.disk.FolderRepository;
import de.dal33t.powerfolder.disk.ScanResult;
import de.dal33t.powerfolder.event.FolderAdapter;
import de.dal33t.powerfolder.event.FolderAutoCreateEvent;
import de.dal33t.powerfolder.event.FolderAutoCreateListener;
import de.dal33t.powerfolder.event.FolderEvent;
import de.dal33t.powerfolder.event.FolderListener;
import de.dal33t.powerfolder.event.FolderRepositoryEvent;
import de.dal33t.powerfolder.event.FolderRepositoryListener;
import de.dal33t.powerfolder.event.InvitationHandler;
import de.dal33t.powerfolder.event.LocalMassDeletionEvent;
import de.dal33t.powerfolder.event.MassDeletionHandler;
import de.dal33t.powerfolder.event.PausedModeEvent;
import de.dal33t.powerfolder.event.PausedModeListener;
import de.dal33t.powerfolder.event.RemoteMassDeletionEvent;
import de.dal33t.powerfolder.event.TransferManagerAdapter;
import de.dal33t.powerfolder.event.TransferManagerEvent;
import de.dal33t.powerfolder.light.FileInfo;
import de.dal33t.powerfolder.light.FolderInfo;
import de.dal33t.powerfolder.message.Invitation;
import de.dal33t.powerfolder.security.ChangePreferencesPermission;
import de.dal33t.powerfolder.skin.Skin;
import de.dal33t.powerfolder.ui.dialog.DialogFactory;
import de.dal33t.powerfolder.ui.dialog.GenericDialogType;
import de.dal33t.powerfolder.ui.dialog.PauseDialog;
import de.dal33t.powerfolder.ui.dialog.SingleFileTransferDialog;
import de.dal33t.powerfolder.ui.information.InformationFrame;
import de.dal33t.powerfolder.ui.model.ApplicationModel;
import de.dal33t.powerfolder.ui.model.BoundPermission;
import de.dal33t.powerfolder.ui.model.TransferManagerModel;
import de.dal33t.powerfolder.ui.notices.FolderAutoCreateNotice;
import de.dal33t.powerfolder.ui.notices.InvitationNotice;
import de.dal33t.powerfolder.ui.notices.LocalDeleteNotice;
import de.dal33t.powerfolder.ui.notices.Notice;
import de.dal33t.powerfolder.ui.notices.OutOfMemoryNotice;
import de.dal33t.powerfolder.ui.notices.SimpleNotificationNotice;
import de.dal33t.powerfolder.ui.notices.WarningNotice;
import de.dal33t.powerfolder.ui.notification.PreviewNotificationHandler;
import de.dal33t.powerfolder.ui.preferences.PreferencesDialog;
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.UIUtil;
import de.dal33t.powerfolder.ui.util.update.UIUpdateHandler;
import de.dal33t.powerfolder.util.BrowserLauncher;
import de.dal33t.powerfolder.util.FileUtils;
import de.dal33t.powerfolder.util.Format;
import de.dal33t.powerfolder.util.Translation;
import de.dal33t.powerfolder.util.Util;
import de.dal33t.powerfolder.util.os.OSUtil;
import de.dal33t.powerfolder.util.os.SystemUtil;
import de.dal33t.powerfolder.util.update.Updater;
import de.dal33t.powerfolder.util.update.UpdaterHandler;
/**
* The ui controller.
*
* @author <a href="mailto:totmacher@powerfolder.com">Christian Sprajc </a>
* @version $Revision: 1.86 $
*/
public class UIController extends PFComponent {
public static final int MAIN_FRAME_ID = 0;
public static final int INFO_FRAME_ID = 1;
public static final int WIZARD_DIALOG_ID = 3;
public static final int MAX_RECENTLY_CHANGED_FILES = 20;
private static final String COMMAND_OPEN_UI = "open-ui";
private static final String COMMAND_HIDE_UI = "hide-ui";
private static final String COMMAND_SYNC_ALL = "sync-all";
private static final String COMMAND_EXIT = "exit";
private static final String COMMAND_SYNC_SHUTDOWN = "sync-shutdown";
private static final String COMMAND_SYNC_EXIT = "sync-exit";
private static final String COMMAND_WEB = "web";
private static final String COMMAND_PAUSE = "pause";
private static final String COMMAND_RESUME = "resume";
private static final String COMMAND_PREFERENCES = "preferences";
private static final String COMMAND_BROWSE = "browse";
private static final String COMMAND_RECENTLY_CHANGED = "recently-changed-";
private boolean started;
private SplashScreen splash;
private TrayIconManager trayIconManager;
private MainFrame mainFrame;
private SystemMonitorFrame systemMonitorFrame;
private InformationFrame informationFrame;
private WeakReference<JDialog> wizardDialogReference;
// List of pending jobs, execute when ui is opened
private final List<Runnable> pendingJobs;
private Menu sysTrayFoldersMenu;
private MenuItem pauseResumeMenu;
private Menu recentlyChangedMenu;
@SuppressWarnings("unused")
private BoundPermission changePrefsPermission;
// The root of all models
private ApplicationModel applicationModel;
private boolean seenOome;
private TransferManagerModel transferManagerModel;
private FolderListener folderListener;
private final AtomicInteger activeFrame;
private final AtomicBoolean synchronizing = new AtomicBoolean();
private final DelayedUpdater statusUpdater;
private final Map<Long, FileInfo> recentlyChangedFiles = new HashMap<Long, FileInfo>(
MAX_RECENTLY_CHANGED_FILES);
private final MenuItem[] recentMenuItems = new MenuItem[MAX_RECENTLY_CHANGED_FILES];
private final PreferencesDialog preferencesDialog;
/**
* The UI distribution running.
*/
private Skin[] skins;
private Skin activeSkin;
private final DelayedUpdater recentlyChangedUpdater = new DelayedUpdater(
getController(), 5000L);
/**
* Initializes a new UI controller. open UI with #start
*
* @param controller
*/
public UIController(Controller controller) {
super(controller);
activeFrame = new AtomicInteger();
statusUpdater = new DelayedUpdater(getController(), 1000L);
preferencesDialog = new PreferencesDialog(controller);
configureOomeHandler();
// Initialize look and feel / icon set
initSkin();
if (OSUtil.isMacOS()) {
UIUtil.setMacDockImage(Icons.getImageById(Icons.LOGO128X128));
}
pendingJobs = Collections.synchronizedList(new LinkedList<Runnable>());
if (!controller.isStartMinimized()) {
// Show splash if not starting minimized
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
logFiner("Opening splash screen");
splash = new SplashScreen(getController(), 260 * 1000);
}
});
} catch (InterruptedException e) {
logSevere("InterruptedException", e);
} catch (InvocationTargetException e) {
logSevere("InvocationTargetException", e);
}
}
informationFrame = new InformationFrame(getController());
if (Feature.SYSTEM_MONITOR.isEnabled()) {
systemMonitorFrame = new SystemMonitorFrame(getController());
}
started = false;
}
/**
* Configure a handler for OutOfMemoryErrors. Note that the Logger must be
* configured to process Severe messages.
*/
private void configureOomeHandler() {
Handler oomeHandler = new Handler() {
public void publish(LogRecord record) {
Throwable throwable = record.getThrown();
if (throwable instanceof OutOfMemoryError) {
OutOfMemoryError oome = (OutOfMemoryError) throwable;
showOutOfMemoryError(oome);
}
}
public void flush() {
}
public void close() throws SecurityException {
}
};
Logger logger = Logger.getLogger("");
logger.addHandler(oomeHandler);
}
/**
* Starts the UI
*/
public void start() {
if (getController().isVerbose()) {
// EventDispatchThreadHangMonitor.initMonitoring();
// RepaintManager
// .setCurrentManager(new CheckThreadViolationRepaintManager());
}
// The central application model
applicationModel = new ApplicationModel(getController());
applicationModel.initialize();
// create the Frame
mainFrame = new MainFrame(getController());
// create the models
folderListener = new MyFolderListener();
for (Folder folder : getController().getFolderRepository().getFolders())
{
folder.addFolderListener(folderListener);
}
getController().getFolderRepository().addFolderRepositoryListener(
new MyFolderRepositoryListener());
getController().getTransferManager().addListener(
new MyTransferManagerListener());
transferManagerModel = new TransferManagerModel(getController()
.getTransferManager());
transferManagerModel.initialize();
if (OSUtil.isLinux()) {
// PFC-2331
TrayIconManager.whitelistSystray(getController());
}
if (OSUtil.isSystraySupported()) {
initializeSystray();
} else {
logWarning("System tray currently only supported on windows (>98)");
mainFrame.getUIComponent().setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
}
if (getController().isStartMinimized()) {
logInfo("Starting minimized");
}
// Show main window
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
mainFrame.getUIComponent().setVisible(
!OSUtil.isSystraySupported()
|| !getController().isStartMinimized());
if (!getController().isStartMinimized()) {
mainFrame.toFront();
}
}
});
} catch (InterruptedException e) {
logSevere("InterruptedException", e);
} catch (InvocationTargetException e) {
logSevere("InvocationTargetException", e);
}
started = true;
// Process all pending runners
synchronized (pendingJobs) {
if (!pendingJobs.isEmpty()) {
logFiner("Executing " + pendingJobs.size() + " pending ui jobs");
for (Runnable runner : pendingJobs) {
SwingUtilities.invokeLater(runner);
}
}
}
UpdaterHandler updateHandler = new UIUpdateHandler(getController());
Updater.installPeriodicalUpdateCheck(getController(), updateHandler);
getController().addMassDeletionHandler(new MyMassDeletionHandler());
getController().addInvitationHandler(new MyInvitationHandler());
getController().getFolderRepository().addFolderAutoCreateListener(
new MyFolderAutoCreateListener());
}
public void askToPauseResume() {
final boolean silent = getController().isPaused();
if (silent) {
// Resuming - nothing to ask.
getController().schedule(new Runnable() {
public void run() {
getController().setPaused(!silent);
}
}, 0);
} else {
if (PreferencesEntry.SHOW_ASK_FOR_PAUSE
.getValueBoolean(getController()))
{
PauseDialog pd = new PauseDialog(getController());
pd.open();
} else {
getController().schedule(new Runnable() {
public void run() {
getController().setPaused(!silent);
}
}, 0);
}
}
}
private void initializeSystray() {
trayIconManager = new TrayIconManager(this);
PopupMenu menu = new PopupMenu();
TrayIcon trayIcon = trayIconManager.getTrayIcon();
if (trayIcon != null) {
trayIcon.setPopupMenu(menu);
}
ActionListener systrayActionHandler = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (COMMAND_OPEN_UI.equals(e.getActionCommand())) {
mainFrame.toFront();
} else if (COMMAND_HIDE_UI.equals(e.getActionCommand())) {
mainFrame.getUIComponent().setVisible(false);
} else if (COMMAND_EXIT.equals(e.getActionCommand())) {
// Exit to system
if (isShutdownAllowed()) {
getController().exit(0);
}
} else if (COMMAND_SYNC_SHUTDOWN.equals(e.getActionCommand())) {
if (OSUtil.isLinux()) {
FormLayout layout = new FormLayout(
"pref, pref:grow, 3dlu, pref, pref",
"3dlu, pref, 3dlu, pref, 3dlu");
PanelBuilder builder = new PanelBuilder(layout);
CellConstraints cc = new CellConstraints();
builder.add(
new JLabel(Translation
.getTranslation("shutdown.message")), cc.xyw(2,
2, 3));
builder.add(
new JLabel(Translation
.getTranslation("shutdown.prompt")), cc
.xy(2, 4));
JPasswordField textField = new JPasswordField(20);
builder.add(textField, cc.xy(4, 4));
int i = DialogFactory.genericDialog(
getController(),
Translation.getTranslation("shutdown.title"),
builder.getPanel(),
new String[]{
Translation.getTranslation("general.ok"),
Translation.getTranslation("general.cancel")},
0, GenericDialogType.QUESTION);
if (i == 0) {
String password = Util.toString(textField
.getPassword());
getController().performFullSync();
getController().shutdownAfterSync(password);
}
} else {
getController().performFullSync();
getController().shutdownAfterSync(null);
}
} else if (COMMAND_SYNC_EXIT.equals(e.getActionCommand())) {
getController().performFullSync();
getController().exitAfterSync(4);
} else if (COMMAND_SYNC_ALL.equals(e.getActionCommand())) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
getController().performFullSync();
}
});
} else if (COMMAND_WEB.equals(e.getActionCommand())) {
try {
BrowserLauncher.openURL(getController().getOSClient()
.getLoginURLWithCredentials());
} catch (IOException e1) {
logWarning("Unable to goto web portal", e1);
}
} else if (COMMAND_BROWSE.equals(e.getActionCommand())) {
FileUtils.openFile(getController().getFolderRepository()
.getFoldersBasedir());
} else if (COMMAND_PAUSE.equals(e.getActionCommand())
|| COMMAND_RESUME.equals(e.getActionCommand()))
{
askToPauseResume();
} else if (COMMAND_PREFERENCES.equals(e.getActionCommand())) {
preferencesDialog.open();
} else if (e.getActionCommand().startsWith(
COMMAND_RECENTLY_CHANGED))
{
int index = e.getActionCommand().lastIndexOf('-');
String suffix = e.getActionCommand().substring(index + 1);
int item = Integer.valueOf(suffix);
synchronized (recentlyChangedFiles) {
int i = 0;
for (FileInfo fileInfo : recentlyChangedFiles.values())
{
if (i++ == item) {
// Open file in the file browser, checking if
// deleted.
UIController uiController = getController()
.getUIController();
uiController.openFileInformation(fileInfo);
break;
}
}
}
}
}
};
// /////////////////////////
// Open / close menu item //
// /////////////////////////
final MenuItem openUI = new MenuItem(
Translation.getTranslation("systray.show"));
menu.add(openUI);
openUI.setActionCommand(COMMAND_OPEN_UI);
openUI.addActionListener(systrayActionHandler);
// //////
// Web //
// //////
MenuItem item;
if (ConfigurationEntry.WEB_LOGIN_ALLOWED
.getValueBoolean(getController()))
{
item = menu.add(new MenuItem(Translation
.getTranslation("action_open_web_interface.name")));
item.setActionCommand(COMMAND_WEB);
item.addActionListener(systrayActionHandler);
}
// //////////
// Folders //
// //////////
sysTrayFoldersMenu = new Menu(
Translation.getTranslation("general.folder"));
sysTrayFoldersMenu.setEnabled(false);
if (Feature.SYSTRAY_ALL_FOLDERS.isEnabled()) {
menu.add(sysTrayFoldersMenu);
}
// /////////
// Browse //
// /////////
item = menu.add(new MenuItem(Translation
.getTranslation("action_open_folders_base.name")));
item.setActionCommand(COMMAND_BROWSE);
item.addActionListener(systrayActionHandler);
// /////////////////
// Pause / Resume //
// /////////////////
pauseResumeMenu = new MenuItem(
Translation.getTranslation("action_resume_sync.name"));
menu.add(pauseResumeMenu);
pauseResumeMenu.addActionListener(systrayActionHandler);
getController().addPausedModeListener(new MyPausedModeListener());
configurePauseResumeLink();
// /////////
// Recent //
// /////////
recentlyChangedMenu = new Menu(
Translation.getTranslation("uicontroller.recently_changed"));
recentlyChangedMenu.setEnabled(false);
menu.add(recentlyChangedMenu);
for (int i = 0; i < MAX_RECENTLY_CHANGED_FILES; i++) {
recentMenuItems[i] = new MenuItem();
recentMenuItems[i].setActionCommand(COMMAND_RECENTLY_CHANGED + i);
recentMenuItems[i].addActionListener(systrayActionHandler);
}
// //////////////
// Preferences //
// //////////////
final MenuItem prefItem = menu.add(new MenuItem(Translation
.getTranslation("action_open_preferences.name")));
prefItem.setActionCommand(COMMAND_PREFERENCES);
prefItem.addActionListener(systrayActionHandler);
changePrefsPermission = new BoundPermission(getController(),
ChangePreferencesPermission.INSTANCE)
{
@Override
public void hasPermission(boolean hasPermission) {
prefItem.setEnabled(hasPermission);
}
};
menu.addSeparator();
// ////////////////
// Sync Shutdown //
// ////////////////
if (SystemUtil.isShutdownSupported()) {
item = menu.add(new MenuItem(Translation
.getTranslation("systray.sync_shutdown")));
item.setActionCommand(COMMAND_SYNC_SHUTDOWN);
item.addActionListener(systrayActionHandler);
}
// ////////////
// Sync Exit //
// ////////////
item = menu.add(new MenuItem(Translation
.getTranslation("systray.sync_exit")));
item.setActionCommand(COMMAND_SYNC_EXIT);
item.addActionListener(systrayActionHandler);
// ///////
// Exit //
// ///////
item = menu
.add(new MenuItem(Translation.getTranslation("systray.exit")));
item.setActionCommand(COMMAND_EXIT);
item.addActionListener(systrayActionHandler);
if (trayIcon != null) {
trayIcon.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
mainFrame.toFront();
}
});
}
try {
SystemTray.getSystemTray().add(trayIcon);
} catch (Exception e) {
logSevere("Exception", e);
OSUtil.disableSystray();
return;
}
// Switch Systray show/hide menuitem dynamically
mainFrame.getUIComponent().addComponentListener(new ComponentAdapter() {
public void componentShown(ComponentEvent arg0) {
openUI.setLabel(Translation.getTranslation("systray.hide"));
openUI.setActionCommand(COMMAND_HIDE_UI);
}
public void componentHidden(ComponentEvent arg0) {
openUI.setLabel(Translation.getTranslation("systray.show"));
openUI.setActionCommand(COMMAND_OPEN_UI);
}
});
mainFrame.getUIComponent().addWindowListener(new WindowAdapter() {
@Override
public void windowIconified(WindowEvent e) {
openUI.setLabel(Translation.getTranslation("systray.show"));
openUI.setActionCommand(COMMAND_OPEN_UI);
}
});
// Load initial folders in menu.
for (Folder folder : getController().getFolderRepository().getFolders())
{
addFolderToSysTray(folder);
}
}
/**
* Add a folder to the SysTray menu structure.
*
* @param folder
*/
private void addFolderToSysTray(Folder folder) {
MenuItem menuItem = new MenuItem(folder.getName());
// Insert in the correct position.
boolean done = false;
for (int i = 0; i < sysTrayFoldersMenu.getItemCount(); i++) {
if (sysTrayFoldersMenu.getItem(i).getLabel().toLowerCase()
.compareTo(folder.getName().toLowerCase()) > 0)
{
sysTrayFoldersMenu.insert(menuItem, i);
done = true;
break;
}
}
if (!done) {
sysTrayFoldersMenu.add(menuItem);
}
sysTrayFoldersMenu.setEnabled(true);
final File localBase = folder.getCommitOrLocalDir();
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (localBase.exists()) {
FileUtils.openFile(localBase);
}
}
});
}
/**
* Remove a folder from the SysTray menu structure.
*
* @param folder
*/
private void removeFolderFromSysTray(Folder folder) {
for (int i = 0; i < sysTrayFoldersMenu.getItemCount(); i++) {
MenuItem menuItem = sysTrayFoldersMenu.getItem(i);
if (menuItem.getLabel().equals(folder.getName())) {
sysTrayFoldersMenu.remove(i);
}
}
if (sysTrayFoldersMenu.getItemCount() == 0) {
sysTrayFoldersMenu.setEnabled(false);
}
}
public void hideSplash() {
if (splash != null) {
splash.shutdown();
}
}
public TransferManagerModel getTransferManagerModel() {
return transferManagerModel;
}
/**
* @return the available skins - may be empty;
*/
public Skin[] getSkins() {
return skins;
}
/**
* @return the active skin - may be null.
*/
public Skin getActiveSkin() {
return activeSkin;
}
private void initSkin() {
List<Skin> skinList = new ArrayList<Skin>();
// Now all skins (defaults + additional skins)
ServiceLoader<Skin> skinLoader = ServiceLoader.load(Skin.class);
for (Skin sk : skinLoader) {
logFine("Loading skin " + sk.getName());
skinList.add(sk);
}
skins = new Skin[skinList.size()];
int i = 0;
for (Skin skin : skinList) {
// Check for dupes.
for (int j = 0; j < i; j++) {
if (skins[j].getName().equals(skin.getName())) {
logSevere("Multiple skins with name: " + skin.getName());
}
}
skins[i++] = skin;
}
String skinName = PreferencesEntry.SKIN_NAME
.getValueString(getController());
boolean found = false;
for (Skin skin : skins) {
if (skin.getName().equals(skinName)) {
activeSkin = skin;
found = true;
break;
}
}
if (!found) {
// Can not find one with this name - use the first one.
activeSkin = skins[0];
PreferencesEntry.SKIN_NAME.setValue(getController(),
activeSkin.getName());
}
Properties props = activeSkin.getIconsProperties();
if (props != null) {
Icons.setIconProperties(props);
}
try {
LookAndFeelSupport.setLookAndFeel(activeSkin.getLookAndFeel());
} catch (UnsupportedLookAndFeelException e) {
logSevere(
"Failed to set look and feel for skin " + activeSkin.getName(),
e);
} catch (ParseException e) {
logSevere(
"Failed to set look and feel for skin " + activeSkin.getName(),
e);
}
}
/**
* Shows an OutOfMemoryError to the user.
*
* @param oome
*/
public void showOutOfMemoryError(OutOfMemoryError oome) {
if (!seenOome) {
seenOome = true;
applicationModel.getNoticesModel().handleNotice(
new OutOfMemoryNotice(oome));
}
}
/**
* Displays the information window if not already displayed.
*/
public void displaySystemMonitorWindow() {
if (systemMonitorFrame != null) {
UIUtil.putOnScreen(systemMonitorFrame.getUIComponent());
systemMonitorFrame.getUIComponent().setVisible(true);
}
}
/**
* Displays the information window if not already displayed.
*/
private void displayInformationWindow() {
mainFrame.showInlineInfoPanel((JPanel) informationFrame
.getUIComponent().getContentPane(), informationFrame
.getUIComponent().getTitle());
}
public void openFileInformation(FileInfo fileInfo) {
informationFrame.displayFile(fileInfo);
displayInformationWindow();
}
/**
* Opens the Files information for a folder.
*
* @param folderInfo
* info of the folder to display files information for.
*/
public void openFilesInformationLatest(FolderInfo folderInfo) {
informationFrame.displayFolderFilesLatest(folderInfo);
displayInformationWindow();
}
public void openFilesInformationDeleted(FolderInfo folderInfo) {
informationFrame.displayFolderFilesDeleted(folderInfo);
displayInformationWindow();
}
public void openFilesInformationUnsynced(FolderInfo folderInfo) {
informationFrame.displayFolderFilesUnsynced(folderInfo);
displayInformationWindow();
}
/**
* Opens the Files information for a folder.
*
* @param folderInfo
* info of the folder to display files information for.
*/
public void openFilesInformation(FolderInfo folderInfo) {
informationFrame.displayFolderFiles(folderInfo);
displayInformationWindow();
}
/**
* Opens the Settings information for a folder.
*
* @param folderInfo
* info of the folder to display member settings information for.
*/
public void openSettingsInformation(FolderInfo folderInfo) {
informationFrame.displayFolderSettings(folderInfo);
displayInformationWindow();
}
/**
* Displays the Settings information move folder dialog.
*
* @param folderInfo
* info of the folder to display member settings information for.
*/
public void moveLocalFolder(FolderInfo folderInfo) {
informationFrame.moveLocalFolder(folderInfo);
}
/**
* Opens the Members information for a folder.
*
* @param folderInfo
* info of the folder to display member computer information for.
*/
public void openMembersInformation(FolderInfo folderInfo) {
informationFrame.displayFolderMembers(folderInfo);
displayInformationWindow();
}
/**
* Opens the Problems information for a folder.
*
* @param folderInfo
* info of the folder to display problems information for.
*/
public void openProblemsInformation(FolderInfo folderInfo) {
informationFrame.displayFolderProblems(folderInfo);
displayInformationWindow();
}
public void openTransfersInformation() {
informationFrame.displayTransfers();
displayInformationWindow();
}
public void openDebugInformation() {
informationFrame.displayDebug();
displayInformationWindow();
}
public void openNoticesCard() {
informationFrame.displayNotices();
displayInformationWindow();
}
/**
* Call when non-quitOnX close called. Hides child frames.
*/
public void hideChildPanels() {
informationFrame.getUIComponent().setVisible(false);
if (systemMonitorFrame != null) {
systemMonitorFrame.getUIComponent().setVisible(false);
}
}
/**
* Handles single file transfer requests. Displays dialog to send offer to
* member.
*
* @param file
* @param node
*/
public void transferSingleFile(File file, Member node) {
SingleFileTransferDialog sftd = new SingleFileTransferDialog(
getController(), file, node);
sftd.open();
}
/**
* Attention: If possible use method
* {@link UIUtil#getParentWindow(ActionEvent)} to determine the active
* window.
* <p>
* This returns most recently active PowerFolder frame. Possibly the
* InformationFrame or (default) MainFrame. Used by dialogs, so focus does
* not always jump to the wrong (Main) frame.
* <P>
*
* @return the active frame.
*/
public Window getActiveFrame() {
int f = activeFrame.get();
if (f == INFO_FRAME_ID) {
JFrame infoComponent = informationFrame.getUIComponent();
if (infoComponent.isVisible()) {
return infoComponent;
}
} else if (f == WIZARD_DIALOG_ID) {
if (wizardDialogReference != null) {
JDialog wizardDialog = wizardDialogReference.get();
if (wizardDialog != null) {
return wizardDialog;
}
}
}
// Default - main frame
return mainFrame.getUIComponent();
}
public void setActiveFrame(int activeFrameId) {
activeFrame.set(activeFrameId);
}
public void setWizardDialogReference(JDialog wizardDialog) {
wizardDialogReference = new WeakReference<JDialog>(wizardDialog);
}
/**
* Shuts the ui down
*/
public void shutdown() {
hideSplash();
if (started) {
informationFrame.getUIComponent().setVisible(false);
informationFrame.getUIComponent().dispose();
if (systemMonitorFrame != null) {
systemMonitorFrame.storeValues();
systemMonitorFrame.getUIComponent().setVisible(false);
systemMonitorFrame.getUIComponent().dispose();
}
mainFrame.storeValues();
mainFrame.getUIComponent().setVisible(false);
mainFrame.getUIComponent().dispose();
// Close systray
if (OSUtil.isSystraySupported() && trayIconManager != null) {
SystemTray.getSystemTray()
.remove(trayIconManager.getTrayIcon());
}
}
started = false;
}
/**
* @return true if the ui controller is started
*/
public boolean isStarted() {
return started;
}
/**
* @return true if the information frame is showing a folder.
*/
public boolean isShowingFolder() {
return isShowingInfo() && informationFrame.isShowingFolder();
}
/**
* @return true if the info panel is displayed currently
*/
public boolean isShowingInfo() {
return mainFrame.isShowingInfoInline();
}
/**
* Sets the loading percentage
*
* @param percentage
* @param nextPercentage
*/
public void setLoadingCompletion(int percentage, int nextPercentage) {
if (splash != null) {
splash.setCompletionPercentage(percentage, nextPercentage);
}
}
// Exposing ***************************************************************
/**
* @return the mainframe
*/
public MainFrame getMainFrame() {
return mainFrame;
}
/**
* For a more convenience way you can also use
* PFUIComponent.getApplicationModel()
*
* @return the application model
* @see PFUIComponent#getApplicationModel()
*/
public ApplicationModel getApplicationModel() {
return applicationModel;
}
// Message dialog helpers *************************************************
/**
* Invokes a runner for later processing. It is ENSURED, that UI is open,
* when the runner is executed
*
* @param runner
*/
public void invokeLater(Runnable runner) {
if (started) {
SwingUtilities.invokeLater(runner);
} else {
logFine("Added runner to pending jobs: " + runner);
// Add to pending jobs
pendingJobs.add(runner);
}
}
/**
* Only use this for preview from the DialogSettingsTab.
*
* @param title
* @param message
*/
public void previewMessage(String title, String message) {
PreviewNotificationHandler notificationHandler = new PreviewNotificationHandler(
getController(), title, message);
notificationHandler.show();
}
private void handleFolderAutoCreate(FolderAutoCreateEvent event) {
applicationModel.getNoticesModel().handleNotice(
new FolderAutoCreateNotice(event.getFolderInfo()));
}
/**
* Scan results have been created after the user requested folder sync. So
* give the user some feedback.
*
* @param scanResult
*/
public void scanResultCreated(ScanResult scanResult) {
// UI hidden?
if (mainFrame == null || mainFrame.isIconifiedOrHidden()) {
return;
}
int newSize = scanResult.getNewFiles().size();
int changedSize = scanResult.getChangedFiles().size();
int deletedSize = scanResult.getDeletedFiles().size();
StringBuilder sb = new StringBuilder();
sb.append(Translation.getTranslation("uicontroller.sync_info.start")
+ "\n\n" + '(');
boolean addComma = false;
if (newSize > 0) {
sb.append(Translation.getTranslation("uicontroller.sync_info.new",
String.valueOf(newSize)));
addComma = true;
}
if (changedSize > 0) {
if (addComma) {
sb.append(", ");
}
sb.append(Translation.getTranslation(
"uicontroller.sync_info.changed", String.valueOf(changedSize)));
addComma = true;
}
if (deletedSize > 0) {
if (addComma) {
sb.append(", ");
}
sb.append(Translation.getTranslation(
"uicontroller.sync_info.deleted", String.valueOf(deletedSize)));
}
if (newSize == 0 && changedSize == 0 && deletedSize == 0) {
sb.append(Translation
.getTranslation("uicontroller.sync_info.no_changes_detected"));
}
sb.append(')');
if (newSize > 0 || changedSize > 0) {
sb.append("\n\n");
sb.append(Translation.getTranslation(
"uicontroller.sync_info.transfer",
String.valueOf(newSize + changedSize)));
}
DialogFactory.genericDialog(getController(),
Translation.getTranslation("uicontroller.sync_info.title"),
sb.toString(), GenericDialogType.INFO);
}
// public void clearBlink() {
// if (trayIconManager != null) {
// trayIconManager.clearBlink();
// }
// }
/**
* Special case. A folder has just been created from an invite. Switch to
* the folder tab and crack open the new folder info.
*
* @param folderInfo
*/
public void displayInviteFolderContents(FolderInfo folderInfo) {
mainFrame.showFoldersTab();
openFilesInformation(folderInfo);
}
private void configurePauseResumeLink() {
if (getController().isPaused()) {
pauseResumeMenu.setLabel(Translation
.getTranslation("action_resume_sync.name"));
pauseResumeMenu.setActionCommand(COMMAND_RESUME);
} else {
pauseResumeMenu.setLabel(Translation
.getTranslation("action_pause_sync.name"));
pauseResumeMenu.setActionCommand(COMMAND_PAUSE);
}
}
private void checkStatus() {
// logInfo("From", new RuntimeException());
statusUpdater.schedule(new Runnable() {
public void run() {
checkStatus0();
}
});
}
/**
* Display folder synchronization info. A copy of the MyFolders quick info
* panel text.
*/
private void checkStatus0() {
long nTotalBytes = 0;
FolderRepository repo = getController().getFolderRepository();
Collection<Folder> folders = repo.getFolders();
int synchronizingFolders = 0;
for (Folder folder : folders) {
if (folder.isTransferring()
|| Double.compare(folder.getStatistic()
.getAverageSyncPercentage(), 100.0d) != 0)
{
synchronizingFolders++;
}
nTotalBytes += folder.getStatistic().getTotalSize();
}
String text1;
boolean changed = false;
synchronized (synchronizing) {
if (synchronizingFolders == 0) {
text1 = Translation.getTranslation("check_status.in_sync_all");
if (synchronizing.get()) {
changed = true;
synchronizing.set(false);
}
}
else {
text1 = Translation.getTranslation("check_status.syncing",
String.valueOf(synchronizingFolders));
if (!synchronizing.get()) {
changed = true;
synchronizing.set(true);
}
}
}
// Disabled popup of sync start.
if (changed) {
String text2 = Translation.getTranslation(
"check_status.powerfolders", Format.formatBytes(nTotalBytes),
String.valueOf(folders.size()));
applicationModel.getNoticesModel().handleNotice(
new SimpleNotificationNotice(Translation
.getTranslation("check_status.title"), text1 + "\n\n"
+ text2));
}
}
/**
* Maintain a list of the most recently changed files.
*
* @param fileInfo
*/
private void addRecentFileChange(FileInfo fileInfo) {
if (recentlyChangedMenu == null) {
return;
}
if (fileInfo.getFolderInfo().isMetaFolder()) {
return;
}
if (fileInfo.getFolder(getController().getFolderRepository())
.getDiskItemFilter().isExcluded(fileInfo))
{
return;
}
synchronized (recentlyChangedFiles) {
// Only keep latest version of any particular file; remove earlier
// versions.
for (Iterator<Long> iterator = recentlyChangedFiles.keySet()
.iterator(); iterator.hasNext();)
{
Long next = iterator.next();
FileInfo info = recentlyChangedFiles.get(next);
if (fileInfo.getRelativeName().equals(info.getRelativeName())) {
iterator.remove();
}
}
Long time = new Date().getTime();
while (recentlyChangedFiles.containsKey(time)) {
// Get a unique time for the key.
time += 1;
}
recentlyChangedFiles.put(time, fileInfo);
// Find the earliest change.
if (recentlyChangedFiles.size() > MAX_RECENTLY_CHANGED_FILES) {
Long first = recentlyChangedFiles.keySet().iterator().next();
recentlyChangedFiles.remove(first);
}
}
// Delay updating the actual menu so we don't spam the UI with multiple
// updates.
recentlyChangedUpdater.schedule(new Runnable() {
public void run() {
// Update menu.
synchronized (recentlyChangedFiles) {
recentlyChangedMenu.removeAll();
int i = 0;
for (FileInfo info : recentlyChangedFiles.values()) {
MenuItem menuItem = recentMenuItems[i++];
recentlyChangedMenu.add(menuItem);
menuItem.setLabel(info.getFilenameOnly());
}
recentlyChangedMenu.setEnabled(!recentlyChangedFiles
.isEmpty());
}
}
});
}
public void closePreferencesDialog() {
if (preferencesDialog != null) {
preferencesDialog.close();
}
}
public void openPreferences() {
if (preferencesDialog != null) {
preferencesDialog.open();
}
}
// ////////////////
// Inner Classes //
// ////////////////
private class MyFolderRepositoryListener implements
FolderRepositoryListener
{
public void folderRemoved(FolderRepositoryEvent e) {
removeFolderFromSysTray(e.getFolder());
e.getFolder().removeFolderListener(folderListener);
checkStatus();
}
public void folderCreated(FolderRepositoryEvent e) {
addFolderToSysTray(e.getFolder());
e.getFolder().addFolderListener(folderListener);
checkStatus();
}
public void maintenanceStarted(FolderRepositoryEvent e) {
}
public void maintenanceFinished(FolderRepositoryEvent e) {
}
public boolean fireInEventDispatchThread() {
return false;
}
}
private class MyFolderListener extends FolderAdapter {
public boolean fireInEventDispatchThread() {
return false;
}
public void statisticsCalculated(FolderEvent folderEvent) {
// logWarning("Stats calced for " + folderEvent.getFolder(), new
// RuntimeException());
checkStatus();
}
public void fileChanged(FolderEvent folderEvent) {
Collection<FileInfo> collection = folderEvent.getScannedFileInfos();
if (collection != null) {
for (FileInfo fileInfo : collection) {
if (!fileInfo.isDiretory()) {
addRecentFileChange(fileInfo);
}
}
}
}
public void filesDeleted(FolderEvent folderEvent) {
Collection<FileInfo> collection = folderEvent.getDeletedFileInfos();
if (collection != null) {
for (FileInfo fileInfo : collection) {
if (!fileInfo.isDiretory()) {
addRecentFileChange(fileInfo);
}
}
}
}
}
private class MyTransferManagerListener extends TransferManagerAdapter {
public boolean fireInEventDispatchThread() {
return false;
}
public void downloadQueued(TransferManagerEvent event) {
checkStatus();
}
public void downloadStarted(TransferManagerEvent event) {
checkStatus();
}
public void downloadAborted(TransferManagerEvent event) {
checkStatus();
}
public void downloadBroken(TransferManagerEvent event) {
checkStatus();
}
public void downloadCompleted(TransferManagerEvent event) {
checkStatus();
}
public void uploadStarted(TransferManagerEvent event) {
checkStatus();
}
public void uploadAborted(TransferManagerEvent event) {
checkStatus();
}
public void uploadBroken(TransferManagerEvent event) {
checkStatus();
}
public void uploadCompleted(TransferManagerEvent event) {
checkStatus();
}
}
/**
* Class to handle local and remote mass deletion events. This pushes
* warnings into the app model.
*/
private class MyMassDeletionHandler implements MassDeletionHandler {
public void localMassDeletion(LocalMassDeletionEvent event) {
LocalDeleteNotice notice = new LocalDeleteNotice(
Translation.getTranslation("warning_notice.title"),
Translation.getTranslation("warning_notice.mass_deletion"),
event.getFolderInfo());
applicationModel.getNoticesModel().handleNotice(notice);
}
public void remoteMassDeletion(RemoteMassDeletionEvent event) {
String message;
if (event.isPercentage()) {
message = Translation.getTranslation(
"uicontroller.remote_mass_delete.warning_message", event
.getMemberInfo().nick, String.valueOf(event
.getDeleteFigure()), event.getFolderInfo().name, event
.getOldProfile().getName(), event.getNewProfile()
.getName());
} else {
message = Translation.getTranslation(
"uicontroller.remote_mass_delete.warning_absolute_message",
event.getMemberInfo().nick, String.valueOf(event
.getDeleteFigure()), event.getFolderInfo().name, event
.getOldProfile().getName(), event.getNewProfile()
.getName());
}
WarningNotice notice = new WarningNotice(
Translation.getTranslation("warning_notice.title"),
Translation.getTranslation("warning_notice.mass_deletion"),
message);
applicationModel.getNoticesModel().handleNotice(notice);
}
}
private class MyInvitationHandler implements InvitationHandler {
public void gotInvitation(Invitation invitation) {
boolean autoAccepted = false;
if (ConfigurationEntry.AUTO_SETUP_ACCOUNT_FOLDERS
.getValueBoolean(getController()))
{
// Automatically accept this invitation, if possible.
autoAccepted = getController().getFolderRepository()
.autoAcceptInvitation(invitation);
}
if (autoAccepted) {
// Just tell the user what happened
Notice notice = new SimpleNotificationNotice(
Translation.getTranslation("notice.invitation.title"),
Translation.getTranslation("notice.invitation.summary",
invitation.getBestUsername(), invitation.folder.name));
applicationModel.getNoticesModel().handleNotice(notice);
} else {
// Let user decide what to do with the invitation.
Notice notice = new InvitationNotice(
Translation.getTranslation("notice.invitation.title"),
Translation.getTranslation("notice.invitation.summary",
invitation.getBestUsername(), invitation.folder.name),
invitation);
applicationModel.getNoticesModel().handleNotice(notice);
}
}
}
private class MyFolderAutoCreateListener implements
FolderAutoCreateListener
{
public boolean fireInEventDispatchThread() {
return true;
}
public void folderAutoCreated(FolderAutoCreateEvent e) {
handleFolderAutoCreate(e);
}
}
/**
* Can we shut down? If WARN_ON_CLOSE, let user know if there are any
* folders still syncing.
*
* @return if all clear to shut down.
*/
public boolean isShutdownAllowed() {
boolean warnOnClose = PreferencesEntry.WARN_ON_CLOSE
.getValueBoolean(getController());
if (warnOnClose) {
Collection<Folder> folderCollection = getController()
.getFolderRepository().getFolders();
List<Folder> foldersToWarn = new ArrayList<Folder>(
folderCollection.size());
for (Folder folder : folderCollection) {
if (folder.isTransferring()) {
logWarning("Close warning on folder: " + folder);
foldersToWarn.add(folder);
}
}
if (!foldersToWarn.isEmpty()) {
StringBuilder folderslist = new StringBuilder();
for (Folder folder : foldersToWarn) {
folderslist.append("\n - " + folder.getName());
}
String title = Translation
.getTranslation("uicontroller.warn_on_close.title");
String text;
if (applicationModel.getFolderRepositoryModel().isSyncing()) {
Date syncDate = applicationModel.getFolderRepositoryModel()
.getEstimatedSyncDate();
text = Translation.getTranslation(
"uicontroller.warn_on_close_eta.text",
folderslist.toString(),
Format.formatDateShort(syncDate));
} else {
text = Translation.getTranslation(
"uicontroller.warn_on_close.text",
folderslist.toString());
}
String question = Translation
.getTranslation("general.neverAskAgain");
NeverAskAgainResponse response = DialogFactory.genericDialog(
getController(),
title,
text,
new String[]{
Translation
.getTranslation("uicontroller.continue_exit"),
Translation.getTranslation("general.cancel")}, 0,
GenericDialogType.QUESTION, question);
if (response.isNeverAskAgain()) {
PreferencesEntry.WARN_ON_CLOSE.setValue(getController(),
false);
}
return response.getButtonIndex() == 0;
}
// No folders unsynced
return true;
}
// Do not warn on close, so we allow shut down
return true;
}
private class MyPausedModeListener implements PausedModeListener {
public boolean fireInEventDispatchThread() {
return true;
}
public void setPausedMode(PausedModeEvent event) {
configurePauseResumeLink();
}
}
}