/** * 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; } }