/**
* Browser.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see .
*/
package org.expeditee.gui;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.net.Authenticator;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import org.expeditee.AbsoluteLayout;
import org.expeditee.actions.Actions;
import org.expeditee.actions.Simple;
import org.expeditee.agents.mail.MailSession;
import org.expeditee.importer.FrameDNDTransferHandler;
import org.expeditee.io.ExpClipReader;
import org.expeditee.io.ItemSelection;
import org.expeditee.io.ProxyAuth;
import org.expeditee.io.ItemSelection.ExpDataHandler;
import org.expeditee.items.Item;
import org.expeditee.items.ItemUtils;
import org.expeditee.items.Text;
import org.expeditee.items.widgets.WidgetCacheManager;
import org.expeditee.network.FrameShare;
import org.expeditee.settings.Settings;
import org.expeditee.settings.UserSettings;
import org.expeditee.stats.Logger;
import org.expeditee.stats.StatsLogger;
import org.expeditee.taskmanagement.EntitySaveManager;
import org.expeditee.taskmanagement.SaveStateChangedEvent;
import org.expeditee.taskmanagement.SaveStateChangedEventListener;
/**
* The Main GUI class, comprises what people will see on the screen.
* Note: Each Object (Item) is responsible for drawing itself on the screen.
* Note2: The Frame is registered as a MouseListener and KeyListener, and
* processes any Events.
*
* @author jdm18
*
*/
public class Browser extends JFrame implements ComponentListener,
WindowListener, WindowStateListener, SaveStateChangedEventListener {
/**
* Default version - just to stop eclipse from complaining about it.
*/
private static final long serialVersionUID = 1L;
// private static final JScrollPane scrollPane = new JScrollPane();
public static Browser _theBrowser = null;
public static ProxyAuth proxyAuth = new ProxyAuth();
public static boolean _hasExited = false;
private MouseEventRouter _mouseEventRouter;
// A flag which is set once the application is exiting.
private boolean _isExiting = false;
private boolean _minimum_version6 = false;
public boolean isMinimumVersion6() {
return _minimum_version6;
}
private static boolean _initComplete = false;
private static String _startFrame = null;
/**
* Constructs a new Browser object, then launches it
*
* @param args
*/
public static void main(String[] args) {
if(args.length > 0) {
_startFrame = args[0];
if(! Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
_startFrame = _startFrame + "1";
}
// Prepare all expeditee and swing data on the AWT event thread.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// MessageBay.supressMessages(true);
// MessageBay.supressMessages(false);
_theBrowser = new Browser();
DisplayIO.refreshCursor();
_theBrowser.requestFocus();
FrameMouseActions.MouseX = MouseInfo.getPointerInfo()
.getLocation().x
- _theBrowser.getOrigin().x;
FrameMouseActions.MouseY = MouseInfo.getPointerInfo()
.getLocation().y
- _theBrowser.getOrigin().y;
_initComplete = true;
Authenticator.setDefault(proxyAuth);
}
});
}
public Point getOrigin() {
return getContentPane().getLocationOnScreen();
}
/**
* @return The mouse event router used for this browser. Never null after
* browser constructed.
*/
public MouseEventRouter getMouseEventRouter() {
return _mouseEventRouter;
}
/**
* @return
*
* True if the application is about to exit. False if not. Not that this is
* only set once the window is in its closed state (not closing) or if the
* application has explicity being requested to exit.
*
* @see Browser#exit()
*
*/
public boolean isExisting() {
return _isExiting;
}
public static boolean isInitComplete() {
return _initComplete;
}
public void setSizes(Dimension size) {
setSize(size);
setPreferredSize(size);
Dimension paneSize = getContentPane().getSize();
FrameGraphics.setMaxSize(paneSize);
}
private Browser() {
// center the frame on the screen
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
double xpos = screen.getWidth() / 2;
double ypos = screen.getHeight() / 2;
setLocation((int) (xpos - (UserSettings.InitialWidth.get() / 2)),
(int) (ypos - (UserSettings.InitialHeight.get() / 2)));
addWindowListener(this);
addWindowStateListener(this);
DisplayIO.addDisplayIOObserver(WidgetCacheManager.getInstance());
DisplayIO.addDisplayIOObserver(PopupManager.getInstance());
// set up the image used for the icon
try
{
URL iconURL = ClassLoader.getSystemResource("org/expeditee/assets/icons/expediteeicon128.png");
if (iconURL != null)
{
Image localImage = Toolkit.getDefaultToolkit().getImage(iconURL);
this.setIconImage(localImage);
}
}
catch (Exception e)
{
e.printStackTrace();
}
setSizes(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
// set the layout to absolute layout for widgets
this.getContentPane().setLayout(new AbsoluteLayout());
_mouseEventRouter = new MouseEventRouter(getJMenuBar(),
getContentPane());
// enable the glasspane-for capturing all mouse events
this.setGlassPane(_mouseEventRouter);
this.getGlassPane().setVisible(true);
this.getContentPane().setBackground(Color.white);
this.getContentPane().setFocusTraversalKeysEnabled(false);
addComponentListener(this);
pack();
// Reset windows to user specified size
// Must be done after initialising the content pane above!
setSizes(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
// UserSettings.ProfileName.set(FrameIO.ConvertToValidFramesetName(System.getProperty("user.name")));
String userName = UserSettings.ProfileName.get();
//UserSettings.UserName.set(UserSettings.ProfileName.get());
// Load documentation and start pages
FrameUtils.extractResources(false);
// Load fonts before loading any frames so the items on the frames will be able to access their fonts
Text.InitFonts();
Frame profile = loadProfile(userName);
// Need to display errors once things have been init otherwise
// exceptions occur if there are more than four messages neededing to be
// displayed.
Frame defaultProfile = loadProfile(UserSettings.DEFAULT_PROFILE_NAME);
Collection warningMessages = new LinkedList();
warningMessages.addAll(FrameUtils.ParseProfile(defaultProfile));
//Save the cursor if the defaultProfile had a custom cursor
Collection- cursor = null;
if(FreeItems.hasCursor()){
cursor = new ArrayList
- ();
cursor.addAll(FreeItems.getCursor());
}
warningMessages.addAll(FrameUtils.ParseProfile(profile));
if(cursor != null && !FreeItems.hasCursor()){
FreeItems.setCursor(cursor);
}
/*
* See Java bug ID 4016934. They say that window closed events are
* called once the JFrame is disposed.
*/
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// Expeditee handles its own repainting of AWT/Swing components
RepaintManager.setCurrentManager(ExpediteeRepaintManager.getInstance());
// Listen for save status to display during and after runtime
EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
String full_version = System.getProperty("java.version");
String[] version_parts = full_version.split("\\.");
if (version_parts.length>=2) {
String version_str = version_parts[0] + "." + version_parts[1];
double version = Double.parseDouble(version_str);
if (version >= 1.6) {
// Set the drag and drop handler
_minimum_version6 = true;
setTransferHandler(FrameDNDTransferHandler.getInstance());
} else {
System.err.println("Upgrade to a (minimum) of Java 1.6 to enable drag and drop support in Expeditee");
}
}
else {
System.err.println("Unable to parse Java version number " + full_version + " to determin if Drag and Drop supported");
}
try {
warningMessages.addAll(Actions.Init());
Settings.Init();
DisplayIO.Init(this);
// Set visible must be just after DisplayIO.Init for the message box
// to
// be the right size
setVisible(true);
setupGraphics();
// required to accept TAB key
setFocusTraversalKeysEnabled(false);
// Must be loaded after setupGraphics if images are on the frame
// Turn off XRay mode and load the first frame
FrameGraphics.setMode(FrameGraphics.MODE_NORMAL, false);
// Go to the start frame if specified, otherwise go to the profile frame
Frame start = null;
if(_startFrame == null) {
_startFrame = UserSettings.StartFrame.get();
if(_startFrame != null && !Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
_startFrame = _startFrame + "1";
}
if((start = FrameIO.LoadFrame(_startFrame)) != null) {
// Make sure HomeFrame gets set
if (UserSettings.HomeFrame.get() == null)
UserSettings.HomeFrame.set(profile.getName());
// Make sure the user can get back to the profile frame easily
DisplayIO.addToBack(profile);
// Go to the start frame
DisplayIO.setCurrentFrame(start, true);
} else {
// If an invalid start frame was specified, show a warning
if(_startFrame != null) {
warningMessages.add("Unknown frame: " + _startFrame);
}
// Go to the profile frame
FrameUtils.loadFirstFrame(profile);
}
DisplayIO.UpdateTitle();
/*
* I think this can be moved back up to the top of the Go method
* now... It used to crash the program trying to print error
* messages up the top
*/
for (String message : warningMessages)
MessageBay.warningMessage(message);
this.getContentPane().addKeyListener(FrameKeyboardActions.getInstance());
this.addKeyListener(FrameKeyboardActions.getInstance());
_mouseEventRouter.addExpediteeMouseListener(FrameMouseActions.getInstance());
_mouseEventRouter.addExpediteeMouseMotionListener(FrameMouseActions.getInstance());
_mouseEventRouter.addExpediteeMouseWheelListener(FrameMouseActions.getInstance());
// Dont refresh for the profile frame otherwise error messages are shown twice
if (!DisplayIO.getCurrentFrame().equals(profile)) {
FrameKeyboardActions.Refresh();
// If it's the profile frame just reparse it in order to display images/circles/widgets correctly
} else {
FrameUtils.Parse(profile);
}
// setVisible(true);
} catch (Exception e) {
e.printStackTrace();
Logger.Log(e);
}
}
/**
* @param userName
* @return
*/
private Frame loadProfile(String userName) {
Frame profile = FrameIO.LoadProfile(userName);
if (profile == null) {
try {
profile = FrameIO.CreateNewProfile(userName);
} catch (Exception e) {
// TODO tell the user that there was a problem creating the
// profile frame and close nicely
e.printStackTrace();
assert (false);
}
}
return profile;
}
public Graphics2D g;
private void setupGraphics() {
if (g != null)
g.dispose();
g = (Graphics2D) this.getContentPane().getGraphics();
assert (g != null);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setFont(g.getFont().deriveFont(40f));
FrameGraphics.setDisplayGraphics(g);
}
// private int count = 0;
@Override
public void paint(Graphics g) {
// All this does is make sure the screen is repainted when the browser
// is moved so that some of the window is off the edge of the display
// then moved back into view
super.paint(g);
FrameGraphics.ForceRepaint();
// System.out.println("Paint " + count++);
}
/**
* @inheritDoc
*/
public void componentResized(ComponentEvent e) {
setSizes(this.getSize());
setupGraphics();
FrameIO.RefreshCasheImages();
FrameGraphics.ForceRepaint();
}
/**
* @inheritDoc
*/
public void componentMoved(ComponentEvent e) {
// FrameGraphics.setMaxSize(this.getSize());
}
/**
* @inheritDoc
*/
public void componentShown(ComponentEvent e) {
}
/**
* @inheritDoc
*/
public void componentHidden(ComponentEvent e) {
}
public void windowClosing(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
exit();
}
public void windowOpened(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowActivated(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
public void windowStateChanged(WindowEvent e) {
}
public int getDrawingAreaX() {
// return scrollPane.getLocationOnScreen().x;
return this.getLocationOnScreen().x;
}
public int getDrawingAreaY() {
// return scrollPane.getLocationOnScreen().y;
return this.getLocationOnScreen().y;
}
public void saveCompleted(SaveStateChangedEvent event) {
// if (isExisting()) {
// } else {
MessageBay.displayMessage("Save finished!", Color.BLUE);
// }
}
public void saveStarted(SaveStateChangedEvent event) {
// if (isExisting()) {
// } else {
String name = event.getEntity().getSaveName();
if (name == null)
name = "data";
MessageBay.displayMessage("Saving " + name + "...", Color.BLUE);
// }
}
/**
* Closes the browser and ends the application. Performs saving operations -
* halting until saves have completed. Feedback is given to the user while
* the application is exiting. Must call on the swing thread.
*/
public void exit() {
// Set exiting flag
_isExiting = true;
MessageBay.displayMessage("System exiting...");
/**
* TODO: Prompt the user etc.
*/
// TODO: Should we should a popup with a progress bar for user feedback?
// this would be nice and easy to do.
// Exit on a dedicated thread so that feedback can be obtained
new Exiter().start(); // this will exit the application
}
/**
* The system must exit on a different thread other than the swing thread so
* that the save threads can fire save-feedback to the swing thread and thus
* provide user feedback on asynchronous save operations.
*
* @author Brook Novak
*
*/
private class Exiter extends Thread {
@Override
public void run() {
// The final save point for saveable entities
EntitySaveManager.getInstance().saveAll();
try {
EntitySaveManager.getInstance().waitUntilAllSavingFinished();
} catch (InterruptedException e) {
e.printStackTrace();
}
// The final phase must save on the swing thread since dealing with
// the expeditee data model
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// Stop any agents or simple programs
Simple.stop();
Actions.stopAgent();
// Wait for them to stop
try {
MessageBay
.displayMessage("Stopping Simple programs..."); // TODO:
/**
* Only stop if need to...
*/
while (Simple.isProgramRunning()) {
Thread.sleep(100);
/* Brook: What purpose does this serve? */
}
MessageBay.displayMessage("Stopping Agents...");
/* TODO: Only stop if need to... */
while (Actions.isAgentRunning()) {
Thread.sleep(100); // Brook: What purpose does this
// serve?
}
} catch (Exception e) {
}
MessageBay.displayMessage("Saving current frame...");
FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
MessageBay.displayMessage("Saving stats...");
StatsLogger.WriteStatsFile();
if (MailSession.getInstance() != null) {
if (MailSession.getInstance().finalise()) {
// TODO display this message before the finalising
// is done but only if the mail needs closing
MessageBay.displayMessage("Closed ExpMail...");
}
}
if (FrameShare.getInstance() != null) {
MessageBay.displayMessage("Stopping FrameServer...");
FrameShare.getInstance().finalise();
}
MessageBay.displayMessage("System exited");
// Finally remove the messages frameset
FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH);
/*
* Create a new messages folder so that it doesn't throw
* Exceptions when two Expeditee's open at once and the
* second tries to save its messages
*/
File file = new File(FrameIO.MESSAGES_PATH + "messages");
file.mkdirs();
Browser._hasExited = true;
System.exit(0);
}
});
}
}
/**
* Used to set up the the browser for use in testing.
*
* @return
*/
public static Browser initializeForTesting() {
if (Browser._theBrowser == null) {
FrameShare.disableNetworking = true;
MailSession._autoConnect = false;
Browser.main(new String[]{});
try {
while (!isInitComplete()) {
Thread.sleep(10);
}
} catch (Exception e) {
}
}
return _theBrowser;
}
}