package org.limewire.ui.swing.mainframe;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.EventObject;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.ToolTipManager;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
import org.jdesktop.application.Action;
import org.jdesktop.application.ApplicationContext;
import org.jdesktop.application.Resource;
import org.jdesktop.application.SessionStorage;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.limewire.core.api.Application;
import org.limewire.core.impl.MockModule;
import org.limewire.inject.GuiceUtils;
import org.limewire.inject.LimeWireInjectModule;
import org.limewire.inject.Modules;
import org.limewire.inspection.Inspector;
import org.limewire.logging.Log;
import org.limewire.logging.LogFactory;
import org.limewire.ui.swing.LimeWireSwingUiModule;
import org.limewire.ui.swing.browser.LimeMozillaInitializer;
import org.limewire.ui.swing.components.LimeJFrame;
import org.limewire.ui.swing.components.PlainCheckBoxMenuItemUI;
import org.limewire.ui.swing.components.PlainMenuItemUI;
import org.limewire.ui.swing.menu.LimeMenuBar;
import org.limewire.ui.swing.options.OptionsDialog;
import org.limewire.ui.swing.settings.InstallSettings;
import org.limewire.ui.swing.settings.SwingUiSettings;
import org.limewire.ui.swing.shell.ShellAssociationManager;
import org.limewire.ui.swing.tray.TrayExitListener;
import org.limewire.ui.swing.tray.TrayNotifier;
import org.limewire.ui.swing.util.GuiUtils;
import org.limewire.ui.swing.util.SaveDirectoryHandler;
import org.limewire.ui.swing.wizard.SetupWizard;
import org.limewire.util.OSUtils;
import org.mozilla.browser.MozillaInitialization;
import org.mozilla.browser.MozillaInitialization.InitStatus;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Provider;
import com.google.inject.Stage;
/**
* The entry point for the Swing UI. If the real core is desired,
* start from integrated-ui/../Main. The main method in this class
* uses the mock-core.
*/
public class AppFrame extends SingleFrameApplication {
private static final Log LOG = LogFactory.getLog(AppFrame.class);
public static final String STARTUP = "startup";
private boolean isStartup = false;
@Inject private static volatile Injector injector;
private static volatile boolean started;
/** Default background color for panels. */
@Resource private Color bgColor;
@Resource private Color glassPaneColor;
// Icons for JFileChooser bug workaround on Vista.
@Resource private Icon upFolderVistaFixIcon;
@Resource private Icon detailsViewVistaFixIcon;
@Resource private Icon listViewVistaFixIcon;
@Resource private Icon newFolderVistaFixIcon;
@Inject private Application application;
@Inject private LimeWireSwingUI ui;
@Inject private Provider<SetupWizard> setupWizardProvider;
@Inject private Provider<OptionsDialog> options;
@Inject private FramePositioner framePositioner;
@Inject private TrayNotifier trayNotifier;
@Inject private LimeMenuBar limeMenuBar;
@Inject private DelayedShutdownHandler delayedShutdownHandler;
private OptionsDialog lastOptionsDialog;
/** Starts with the mock core. */
public static void main(String[] args) {
launch(AppFrame.class, args);
}
/** Returns true if the UI has initialized & successfully been shown. */
public static boolean isStarted() {
return started;
}
/**
* JDesktop's built-in storage isn't what we want -- don't use it.
* There's issues with it restoring sizes/position if you closed
* while minimized or other weirdness. Unfortunately, changing
* it is package-private.
*/
private void changeSessionStorage(ApplicationContext ctx, SessionStorage storage) {
try {
Method m = ctx.getClass().getDeclaredMethod("setSessionStorage", SessionStorage.class);
m.setAccessible(true);
m.invoke(ctx, storage);
} catch(Throwable oops) {}
}
@Override
protected void initialize(String[] args) {
changeSessionStorage(getContext(), new NullSessionStorage(getContext()));
GuiUtils.assignResources(this);
initUIDefaults();
// Because we use a browser heavily, which is heavyweight,
// we must disable all lightweight popups.
if(MozillaInitialization.getStatus() != InitStatus.FAILED) {
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
ToolTipManager.sharedInstance().setLightWeightPopupEnabled(false);
}
isStartup = args.length > 0 && STARTUP.equals(args[0]);
}
@Override
protected void startup() {
String title = getContext().getResourceMap().getString("Application.title");
JFrame frame = new FirstVizIgnorer(title);
frame.setName("mainFrame");
getMainView().setFrame(frame);
// Create the Injector for the UI.
assert ui == null;
createUiInjector();
assert ui != null;
if(isStartup || SwingUiSettings.MINIMIZE_TO_TRAY.getValue()) {
trayNotifier.showTrayIcon();
} else {
trayNotifier.hideTrayIcon();
}
getMainFrame().setJMenuBar(limeMenuBar);
// Install handler for shutdown after transfers.
delayedShutdownHandler.install(this);
addExitListener(new TrayExitListener(trayNotifier));
addExitListener(new ShutdownListener(getMainFrame(), application));
framePositioner.initialize(getMainFrame());
// This will NOT actually show it -- we're purposely
// doing this to more explicitly control when it goes visible.
// Unfortunately, we have to call show in order to setup things
// on the ui.
show(ui);
// Set the window position just before we become visible.
framePositioner.setWindowPosition();
// Set visible if this isn't being run from startup --
// otherwise, minimize.
if (isStartup) {
minimizeToTray();
} else {
getMainFrame().setVisible(true);
}
ui.goHome();
ui.focusOnSearch();
started = true;
}
@Override
protected void ready() {
if (SetupWizard.shouldShowWizard(application)) {
JXPanel glassPane = new JXPanel();
glassPane.setOpaque(false);
glassPane.setBackgroundPainter(new AbstractPainter<JComponent>() {
@Override
protected void doPaint(Graphics2D g, JComponent object, int width, int height) {
g.setPaint(glassPaneColor);
g.fillRect(0, 0, width, height);
}
});
getMainFrame().setGlassPane(glassPane);
ui.hideMainPanel();
glassPane.setVisible(true);
setupWizardProvider.get().showDialog(getMainFrame(), application);
glassPane.setVisible(false);
ui.showMainPanel();
}
// Make absolutely positively certain that we've set this to true.
InstallSettings.UPGRADED_TO_5.setValue(true);
validateSaveDirectory();
// Now that the UI is ready to use, update it's priority a bit.
Thread eventThread = Thread.currentThread();
eventThread.setPriority(eventThread.getPriority() + 1);
new ShellAssociationManager().validateFileAssociations(getMainFrame());
ui.loadProNag();
}
@Action
public void showOptionsDialog(ActionEvent actionEvent) { // DO NOT CHANGE THIS METHOD NAME!
if(lastOptionsDialog == null) {
lastOptionsDialog = options.get();
}
if (!lastOptionsDialog.isVisible()) {
lastOptionsDialog.initOptions();
lastOptionsDialog.setLocationRelativeTo(GuiUtils.getMainFrame());
if(actionEvent.getID() == ActionEvent.ACTION_PERFORMED && actionEvent.getActionCommand() != null){
lastOptionsDialog.select(actionEvent.getActionCommand());
}
lastOptionsDialog.setVisible(true);
}
}
@Action
public void exitApplication(ActionEvent actionEvent) { // DO NOT CHANGE THIS METHOD NAME!
exit(actionEvent);
}
@Action
public void minimizeToTray() { // DO NOT CHANGE THIS METHOD NAME!
getMainFrame().setState(Frame.ICONIFIED);
getMainFrame().setVisible(!trayNotifier.supportsSystemTray());
}
@Action
public void restoreView() { // DO NOT CHANGE THIS METHOD NAME!
if(!SwingUiSettings.MINIMIZE_TO_TRAY.getValue()) {
trayNotifier.hideTrayIcon();
}
getMainFrame().setVisible(true);
getMainFrame().setState(Frame.NORMAL);
getMainFrame().toFront();
}
/**
* Action method to exit the application after all transfers are completed.
*/
@Action
public void shutdownAfterTransfers() { // DO NOT CHANGE THIS METHOD NAME!
trayNotifier.showTrayIcon();
delayedShutdownHandler.shutdownAfterTransfers();
}
public Injector createUiInjector() {
Module thiz = new AbstractModule() {
@Override
protected void configure() {
bind(AppFrame.class).toInstance(AppFrame.this);
}
};
Injector childInjector;
if (injector == null) {
LimeMozillaInitializer.initialize();
childInjector = Guice.createInjector(Stage.DEVELOPMENT,
new MockModule(),
new LimeWireInjectModule(),
new LimeWireSwingUiModule(false),
thiz);
} else {
// TODO: We want to use child injectors, but weird things happen
// with circular dependencies...
childInjector = Guice.createInjector(Stage.DEVELOPMENT,
Modules.providersFrom(injector),
new LimeWireInjectModule(),
thiz,
new LimeWireSwingUiModule(injector.getInstance(Application.class).isProVersion()),
new AbstractModule() {
@Override
protected void configure() {
// Make sure that the inspector is inspecting on the correct injector.
requestInjection(new Object() {
@SuppressWarnings("unused")
@Inject void setInspectorInjector(Inspector inspector, Injector injector) {
inspector.setInjector(injector);
}
});
}
});
}
GuiceUtils.loadEagerSingletons(childInjector);
return childInjector;
}
/**
* Sets the custom default UI color and behavior properties
*/
private void initUIDefaults() {
if (OSUtils.isMacOSX()) {
initMacUIDefaults();
}
if (OSUtils.isWindows()) {
verifyWindowsLAF();
}
initBackgrounds();
Color originalSelectionBackground
= (Color) UIManager.get("MenuItem.selectionBackground");
Color originalSelectionForeground
= (Color) UIManager.get("MenuItem.selectionForeground");
if (originalSelectionForeground != null && originalSelectionBackground != null) {
PlainMenuItemUI.overrideDefaults(originalSelectionForeground, originalSelectionBackground);
PlainCheckBoxMenuItemUI.overrideDefaults(originalSelectionForeground, originalSelectionBackground);
}
// Set default selection colours
Color selectionBackground = new Color(0xc2e986);
UIManager.put("TextField.selectionBackground", selectionBackground);
UIManager.put("PasswordField.selectionBackground", selectionBackground);
UIManager.put("EditorPane.selectionBackground", selectionBackground);
UIManager.put("TextArea.selectionBackground", selectionBackground);
UIManager.put("MenuItem.selectionBackground", selectionBackground);
UIManager.put("CheckBoxMenuItem.selectionBackground", selectionBackground);
UIManager.put("RadioButtonMenuItem.selectionBackground", selectionBackground);
// Set the menu item highlight colors to avoid contrast issues with
// new highlight background in default XP theme
Color selectionForeground = Color.BLACK;
UIManager.put("TextField.selectionForeground", selectionForeground);
UIManager.put("PasswordField.selectionForeground", selectionForeground);
UIManager.put("EditorPane.selectionForeground", selectionForeground);
UIManager.put("TextArea.selectionForeground", selectionForeground);
UIManager.put("MenuItem.selectionForeground", selectionForeground);
UIManager.put("CheckBoxMenuItem.selectionForeground", selectionForeground);
UIManager.put("RadioButtonMenuItem.selectionForeground", selectionForeground);
// Necessary to allow popups to behave normally.
UIManager.put("PopupMenu.consumeEventOnClose", false);
// FIX FOR SUN BUG 6449933: On Windows sometimes, JFileChooser cannot
// display because some icons throw an AIOOBE when they are retrieved.
// To workaround this, we install our own version of those icons.
if (OSUtils.isWindows()) {
replaceIconIfFailing("FileChooser.upFolderIcon", upFolderVistaFixIcon);
replaceIconIfFailing("FileChooser.detailsViewIcon", detailsViewVistaFixIcon);
replaceIconIfFailing("FileChooser.listViewIcon", listViewVistaFixIcon);
replaceIconIfFailing("FileChooser.newFolderIcon", newFolderVistaFixIcon);
}
}
/**
* Sets some mac only UI settings.
*/
private void initMacUIDefaults() {
UIManager.put("MenuItemUI", "javax.swing.plaf.basic.BasicMenuItemUI");
UIManager.put("CheckBoxMenuItemUI", "javax.swing.plaf.basic.BasicCheckBoxMenuItemUI");
UIManager.put("RadioButtonMenuItemUI", "javax.swing.plaf.basic.BasicRadioButtonMenuItemUI");
}
/**
* Changes all default background colors that are equal to Panel.background to the
* bgColor set in properties. Also sets Table.background.
*/
private void initBackgrounds() {
ColorUIResource bgColorResource = new ColorUIResource(bgColor);
Color oldBgColor = UIManager.getDefaults().getColor("Panel.background");
UIDefaults uiDefaults = UIManager.getDefaults();
Enumeration<?> enumeration = uiDefaults.keys();
while (enumeration.hasMoreElements()) {
Object key = enumeration.nextElement();
if (key.toString().indexOf("background") != -1) {
if (uiDefaults.get(key).equals(oldBgColor)) {
UIManager.getDefaults().put(key, bgColorResource);
}
}
}
uiDefaults.put("Table.background", bgColorResource);
}
/**
* Replaces an icon resource in UIManager with the specified replacement
* icon if the original resource cannot be retrieved correctly.
*/
private void replaceIconIfFailing(String resource, Icon replacementIcon) {
try {
UIManager.getIcon(resource);
} catch (ArrayIndexOutOfBoundsException aioobe) {
UIManager.put(resource, replacementIcon);
}
}
/** Ensures the save directory is valid. */
private void validateSaveDirectory() {
// Make sure the save directory is valid.
SaveDirectoryHandler.validateSaveDirectoryAndPromptForNewOne();
}
/**
* Verify that the Windows LAF will load properly. If not, then this
* method installs the cross-platform LAF. This is a workaround for Sun
* Bug IDs 6629522, 6588271, 6622760, 6637885, which can cause an NPE when
* retrieving the checkbox icon width.
*/
private void verifyWindowsLAF() {
// Skip if installed LAF is not Windows LAF.
String lafName = UIManager.getLookAndFeel().getClass().getName();
if (!UIManager.getSystemLookAndFeelClassName().equals(lafName)) {
return;
}
boolean lafValid = true;
try {
// Create checkbox with sample text, and get its preferred size.
// This should force a call to get the width of the checkbox icon
// in the Windows LAF. The icon class is
// WindowsIconFactory$CheckBoxIcon. The icon can also be retrieved
// by a call to WindowsCheckBoxUI.getDefaultIcon(). (JIRA LWC-2302)
JCheckBox checkBox = new JCheckBox("Verify");
checkBox.getPreferredSize();
} catch (NullPointerException npe) {
LOG.error("Windows XP LAF error", npe);
lafValid = false;
}
// Install cross-platform LAF if Windows LAF is not valid.
if (!lafValid) {
try {
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
} catch (Exception ex) {
LOG.error("Unable to install LAF", ex);
}
}
}
private static class ShutdownListener implements ExitListener {
private final Application application;
private final JFrame mainFrame;
public ShutdownListener(JFrame mainFrame, Application application) {
this.mainFrame = mainFrame;
this.application = application;
}
@Override
public boolean canExit(EventObject event) {
return true;
}
@Override
public void willExit(EventObject event) {
mainFrame.setVisible(false);
System.out.println("Shutting down...");
application.stopCore();
System.out.println("Shut down");
}
}
private static class FirstVizIgnorer extends LimeJFrame {
/** false if we haven't shown once yet. */
private boolean shownOnce = false;
public FirstVizIgnorer(String title) {
super(title);
}
//this is a bit hacky and ugly but necessary because SingleFrameApplication.show(Component) calls setVisible
//and we don't want that to happen. Unfortunately show() also calls private methods so we can't just override it.
@Override
public void setVisible(boolean visible) {
// Ignore the first call.
if(!shownOnce) {
shownOnce = true;
} else {
super.setVisible(visible);
}
}
@SuppressWarnings("deprecation")
@Override
public void hide() {
try {
super.hide();
} catch(Throwable t) {
// Ignored... Internal JDK bugs causing this to error
// out, which ends up stopping us from closing LW.
}
}
}
}