/**
* 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.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import org.expeditee.actions.Actions;
import org.expeditee.actions.Simple;
import org.expeditee.agents.mail.MailSession;
import org.expeditee.auth.AuthenticatorBrowser;
import org.expeditee.core.BlockingRunnable;
import org.expeditee.core.Colour;
import org.expeditee.core.Dimension;
import org.expeditee.core.Point;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.EcosystemManager.Ecosystem;
import org.expeditee.gio.GraphicsManager;
import org.expeditee.gio.InputManager;
import org.expeditee.gio.InputManager.WindowEventListener;
import org.expeditee.gio.InputManager.WindowEventType;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.io.ProxyAuth;
import org.expeditee.items.Item;
import org.expeditee.items.ItemUtils;
import org.expeditee.items.Text;
import org.expeditee.items.widgets.JfxBrowser;
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;
import javafx.application.Platform;
/**
* 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.
*
* TODO List:
*
* Back to standard:
* - JavaFX Text-hitting (requires JFX1.9)
* - Overlays and Vectors review
* - Pop-ups (unused in base Expeditee, only in Apollo)
* - Apollo input (test)
* - The rest of Apollo
* - Make sure clipping/invalidation takes twin-frames into account
* - Reinstate Simple input commands
* - Test LinkedTrack.paintInFreeSpace()
* - Test EmulatedTextItem.onMouseReleased(MouseEvent) (removed emulatedSource mouse button check)
* - Constrained placement of items
*
* Extra:
* - Self-describing input
* - Thread safety (most stuff on GIO event thread but Agents can start new threads)
* - Touch input
* - Better reflection??? (currently changes to the code break reflection as it relies on names)
* - JavaFX lag (always rendering to images doesn't gel well with JavaFX)
* - Swing widgets in JFX
* - JavaFX Widgets (exception on drawing?)
* - Reduce reliance on into-image rendering to improve JavaFX performance (utilise enforced-clip)
* - Block Swing ecosystem setup until window is available, or...
* - Reconfigure window-resized code so things are properly resized (PREFERRED).
* - Swing alpha-compositing of colours (currently alpha is ignored e.g. drop-shadow in transition).
* - Overly-thick extrusion lines (seems to depend on number of connected lines...)
* - Make FreeItems control pickup etc.
* - Remove MouseEventRouter
* - Highlighting (should be controlled by the items themselves)
* - Make Widgets into fully-fledged items (maybe???)
* - Merge Widget and HeavyDutyWidget
* - Redefine TextLayouts (relative/absolute layouts)
* - Settings exceptions (Password widget in JFX) (currently fixed with hack)
* - MessageBay (what did I mean by this specifically?)
* - Order-dependency of start-up code
* - MessageBay not refreshing at start-up
* - Invalidation hierarchy (item => frame => display controller area => window)
* - Add gesture data type checking
* - Paintable interface
* - EcosystemSpecific interface (utilise to enable/disable features based on ecosystem)
* - Remove/modify Mutable class (in Apollo.util, change to InOutReference)
* - Convert BlockingRunnable to interface
* - Modify Metronome to utilise Timeouts
* - Need AWT in FastAlphaEffect???
*
* General:
* - Tidy FrameGraphics
* - Comment
* - UserSettings.DEFAULT_PROFILE_NAME not actually a user setting
*
* Done:
* - Timers
* - Timer input animations
* - Finish DisplayController/FrameGraphics separation
* - Anchor constraints (create class)
* - Make MessageBay take lead from DisplayController
* - Make message bay display again (to do with above)
* - Tooltips (layout broken in Swing as window size not correct when tooltips laid out)
* - Frame transitions
* - Frame transitions in twin-frames mode drawing over each other
* - JFX DnD manager
* - Reinstate Simple
* - Reinstate exclude source
* - Reinstate commented code
* - Twin-frames division 0 at startup
* - Twin frames off-by-one frame size (draws a line of erroneous pixels)
* - MessageBay delays showing messages on start-up
* - JFX Antialiasing done on theScene??? Doesn't apply for our situation
* - Incorporate Clip class into code
* - Enforced clip
*
* @author cts16
* @author jdm18
*/
public class Browser implements SaveStateChangedEventListener {
public static final boolean DEBUG = true;
public static final Ecosystem ECOSYSTEM_TYPE = Ecosystem.Swing;
public static Browser _theBrowser = null;
public static ProxyAuth proxyAuth = new ProxyAuth();
public static boolean _hasExited = false;
/** A flag which is set once the application is exiting. */
protected boolean _isExiting = false;
protected 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 (AuthenticatorBrowser.isAuthenticationRequired()) {
String starting_user_name = System.getProperty("user.name");
System.setProperty("startinguser.name", starting_user_name);
System.setProperty("user.name", AuthenticatorBrowser.USER_NOBODY);
}
// Parse the starting frame command-line argument
if(args.length > 0) {
setStartFrame(args[0]);
if(!Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
setStartFrame(getStartFrame() + "1");
}
} else {
setStartFrame("home1");
}
// Window icon must be set before initialisation
GraphicsManager.setWindowIcon(DisplayController.ICON_IMAGE);
// Setup the GIO ecosystem so it is ready when we need it
EcosystemManager.createEcosystem(ECOSYSTEM_TYPE);
try {
EcosystemManager.getMiscManager().runOnGIOThread(new BlockingRunnable() {
@Override
public void execute() {
init();
MessageBay.showDelayedMessages(true);
}
});
} catch (Throwable e) {
e.printStackTrace(System.err);
System.exit(1);
}
//MessageBay.showDelayedMessages(true);
}
public static void init() {
if (AuthenticatorBrowser.isAuthenticationRequired()) {
try {
_theBrowser = AuthenticatorBrowser.getInstance();
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
} else {
_theBrowser = new Browser();
}
EcosystemManager.getGraphicsManager().requestFocus();
// Java's way of getting access to internet. Authenticating the user with their proxy username and password.
Authenticator.setDefault(proxyAuth);
_initComplete = true;
}
/**
* @return
*
* True if the application is about to exit. False if not. Note that this is
* only set once the window is in its closed state (not closing) or if the
* application has explicitly being requested to exit.
*
* @see Browser#exit()
*
*/
public boolean isExiting() {
return _isExiting;
}
public static boolean isInitComplete() {
return _initComplete;
}
protected Browser(String mode) {
System.out.println("Running Expeditee in " + mode + " mode.");
}
protected Browser() {
// center the frame on the screen
GraphicsManager g = EcosystemManager.getGraphicsManager();
Dimension screen = g.getScreenSize();
double xpos = (screen.width - UserSettings.InitialWidth.get()) / 2.0;
double ypos = (screen.height - UserSettings.InitialHeight.get()) / 2.0;
g.setWindowLocation(new Point((int) xpos, (int) ypos));
DisplayController.Init();
DisplayController.addDisplayObserver(WidgetCacheManager.getInstance());
if (ECOSYSTEM_TYPE == Ecosystem.Swing) {
DisplayController.addDisplayObserver(PopupManager.getInstance());
}
setInputManagerWindowRoutines();
// Reset windows to user specified size
Dimension initialWindowSize = new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get());
g.setWindowSize(initialWindowSize);
// 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();
if (!AuthenticatorBrowser.isAuthenticationRequired() && UserSettings.PublicAndPrivateResources) {
String userName = System.getProperty("user.name"); //UserSettings.ProfileName.get();
if (!FrameIO.personalResourcesExist(userName)) {
FrameIO.setupPersonalResources(userName);
}
}
Settings.Init();
Frame userProfile = loadInitialProfiles();
FrameIO.changeParentAndSubFolders(FrameIO.PARENT_FOLDER);
// Listen for save status to display during and after runtime
EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
try {
MessageBay.warningMessages(Actions.Init());
// Go to the start frame if specified, otherwise go to the profile frame
if (getStartFrame() == null) {
setStartFrame(UserSettings.StartFrame.get());
if (getStartFrame() != null && !Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
setStartFrame(getStartFrame() + "1");
}
}
Frame start = null;
if ((start = FrameIO.LoadFrame(getStartFrame())) != null) {
// Make sure HomeFrame gets set
UserSettings.HomeFrame.set(start.getName());
// Go to the start frame
DisplayController.setCurrentFrame(start, true);
} else {
// If an invalid start frame was specified, show a warning
if (getStartFrame() != null) {
MessageBay.warningMessage("Unknown frame: " + getStartFrame());
}
// Go to the profile frame
FrameUtils.loadFirstFrame(userProfile);
}
DisplayController.updateTitle();
// Don't refresh for the profile frame otherwise error messages are shown twice
if (!DisplayController.getCurrentFrame().equals(userProfile)) {
StandardGestureActions.Refresh();
// If it's the profile frame just reparse it in order to display
// images/circles/widgets correctly
} else {
FrameUtils.Parse(userProfile);
}
} catch (Exception e) {
e.printStackTrace();
Logger.Log(e);
}
}
@Override
public void saveCompleted(SaveStateChangedEvent event)
{
MessageBay.displayMessage("Save finished!", Colour.BLUE);
}
@Override
public void saveStarted(SaveStateChangedEvent event)
{
String name = event.getEntity().getSaveName();
if (name == null) {
name = "data";
}
MessageBay.displayMessage("Saving " + name + "...", Colour.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();
}
// Stop any agents or simple programs
Simple.stop();
Actions.stopAgent();
// Wait for them to stop
try {
// Only stop if need to...
// Brook: What purpose does this serve?
MessageBay.displayMessage("Stopping Simple programs...");
while (Simple.isProgramRunning()) {
Thread.sleep(100);
}
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(DisplayController.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();
}
// Shut down the Java FX Platform.
if (JfxBrowser.JFXBROWSER_IN_USE) {
MessageBay.displayMessage("Detected use of JFX Browser. Stopping Java FX Platform...");
Platform.exit();
}
MessageBay.displayMessage("System exited");
// Finally remove the messages frameset
FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH, false);
/*
* 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;
}
private static void setInputManagerWindowRoutines() {
InputManager manager = EcosystemManager.getInputManager();
// Refresh the layout when the window resizes
manager.addWindowEventListener(new WindowEventListener() {
@Override
public void onWindowEvent(WindowEventType type)
{
if (type != WindowEventType.WINDOW_RESIZED) {
return;
}
DisplayController.refreshWindowSize();
FrameIO.RefreshCacheImages();
for (Frame frame : DisplayController.getFrames()) {
if (frame != null) {
ItemUtils.Justify(frame);
frame.refreshSize();
}
}
DisplayController.requestRefresh(false);
}
});
manager.addWindowEventListener(new WindowEventListener() {
@Override
public void onWindowEvent(WindowEventType type)
{
if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
return;
}
StandardGestureActions.mouseExitedWindow();
}
});
manager.addWindowEventListener(new WindowEventListener() {
@Override
public void onWindowEvent(WindowEventType type)
{
if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
return;
}
StandardGestureActions.mouseEnteredWindow();
}
});
manager.addWindowEventListener(new WindowEventListener() {
@Override
public void onWindowEvent(WindowEventType type)
{
if (type != WindowEventType.WINDOW_CLOSED) {
return;
}
if (Browser._theBrowser != null) {
Browser._theBrowser.exit();
}
}
});
}
/**
* @return The user's profile frame.
*/
public static Frame loadInitialProfiles()
{
String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
String userName = System.getProperty("user.name");
Frame defaultProfile = loadProfile(defaultProfileName);
Frame userProfile = loadProfile(userName);
MessageBay.warningMessages(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());
}
//MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
if (cursor != null && !FreeItems.hasCursor()) {
FreeItems.setCursor(cursor);
}
FrameUtils.ParseProfile(userProfile);
String proFileName = userProfile.getFramesetName();
UserSettings.ProfileName.set(proFileName);
return userProfile;
}
protected static Frame loadProfile(String userName) {
Frame profile = FrameIO.LoadProfile(userName);
if (profile == null) {
try {
profile = FrameIO.CreateNewProfile(userName, null, null);
} 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 static String getStartFrame() {
return _startFrame;
}
public static void setStartFrame(String _startFrame) {
Browser._startFrame = _startFrame;
}
}