source: trunk/src/org/expeditee/gui/Browser.java@ 1521

Last change on this file since 1521 was 1521, checked in by bnemhaus, 4 years ago

MessageBay must display its delayed messages using the AWT event thread (accessed via GIOThread) in order to avoid a race condition to get a lock when doing DisplayController.refresh

File size: 17.9 KB
RevLine 
[919]1/**
2 * Browser.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
[4]19package org.expeditee.gui;
20
[348]21import java.io.File;
[1215]22import java.io.IOException;
[535]23import java.net.Authenticator;
[1215]24import java.security.KeyStoreException;
25import java.security.NoSuchAlgorithmException;
26import java.security.cert.CertificateException;
27import java.sql.SQLException;
[427]28import java.util.ArrayList;
[80]29import java.util.Collection;
[4]30
31import org.expeditee.actions.Actions;
[139]32import org.expeditee.actions.Simple;
[238]33import org.expeditee.agents.mail.MailSession;
[1282]34import org.expeditee.auth.AuthenticatorBrowser;
[1102]35import org.expeditee.core.BlockingRunnable;
36import org.expeditee.core.Colour;
37import org.expeditee.core.Dimension;
38import org.expeditee.core.Point;
39import org.expeditee.gio.EcosystemManager;
40import org.expeditee.gio.EcosystemManager.Ecosystem;
41import org.expeditee.gio.GraphicsManager;
42import org.expeditee.gio.InputManager;
43import org.expeditee.gio.InputManager.WindowEventListener;
44import org.expeditee.gio.InputManager.WindowEventType;
45import org.expeditee.gio.gesture.StandardGestureActions;
[535]46import org.expeditee.io.ProxyAuth;
[427]47import org.expeditee.items.Item;
[1102]48import org.expeditee.items.ItemUtils;
[777]49import org.expeditee.items.Text;
[219]50import org.expeditee.items.widgets.WidgetCacheManager;
[298]51import org.expeditee.network.FrameShare;
[570]52import org.expeditee.settings.Settings;
53import org.expeditee.settings.UserSettings;
[419]54import org.expeditee.stats.Logger;
[139]55import org.expeditee.stats.StatsLogger;
56import org.expeditee.taskmanagement.EntitySaveManager;
57import org.expeditee.taskmanagement.SaveStateChangedEvent;
58import org.expeditee.taskmanagement.SaveStateChangedEventListener;
[4]59
60/**
61 * The Main GUI class, comprises what people will see on the screen.<br>
62 * Note: Each Object (Item) is responsible for drawing itself on the screen.<br>
63 * Note2: The Frame is registered as a MouseListener and KeyListener, and
64 * processes any Events.
65 *
[1102]66 * TODO List:
67 *
68 * Back to standard:
69 * - JavaFX Text-hitting (requires JFX1.9)
70 * - Overlays and Vectors review
71 * - Pop-ups (unused in base Expeditee, only in Apollo)
72 * - Apollo input (test)
73 * - The rest of Apollo
74 * - Make sure clipping/invalidation takes twin-frames into account
75 * - Reinstate Simple input commands
76 * - Test LinkedTrack.paintInFreeSpace()
77 * - Test EmulatedTextItem.onMouseReleased(MouseEvent) (removed emulatedSource mouse button check)
78 * - Constrained placement of items
79 *
80 * Extra:
81 * - Self-describing input
82 * - Thread safety (most stuff on GIO event thread but Agents can start new threads)
83 * - Touch input
84 * - Better reflection??? (currently changes to the code break reflection as it relies on names)
85 * - JavaFX lag (always rendering to images doesn't gel well with JavaFX)
86 * - Swing widgets in JFX
87 * - JavaFX Widgets (exception on drawing?)
88 * - Reduce reliance on into-image rendering to improve JavaFX performance (utilise enforced-clip)
89 * - Block Swing ecosystem setup until window is available, or...
90 * - Reconfigure window-resized code so things are properly resized (PREFERRED).
91 * - Swing alpha-compositing of colours (currently alpha is ignored e.g. drop-shadow in transition).
92 * - Overly-thick extrusion lines (seems to depend on number of connected lines...)
93 * - Make FreeItems control pickup etc.
94 * - Remove MouseEventRouter
95 * - Highlighting (should be controlled by the items themselves)
96 * - Make Widgets into fully-fledged items (maybe???)
97 * - Merge Widget and HeavyDutyWidget
98 * - Redefine TextLayouts (relative/absolute layouts)
99 * - Settings exceptions (Password widget in JFX) (currently fixed with hack)
100 * - MessageBay (what did I mean by this specifically?)
101 * - Order-dependency of start-up code
102 * - MessageBay not refreshing at start-up
103 * - Invalidation hierarchy (item => frame => display controller area => window)
104 * - Add gesture data type checking
105 * - Paintable interface
106 * - EcosystemSpecific interface (utilise to enable/disable features based on ecosystem)
107 * - Remove/modify Mutable class (in Apollo.util, change to InOutReference)
108 * - Convert BlockingRunnable to interface
109 * - Modify Metronome to utilise Timeouts
110 * - Need AWT in FastAlphaEffect???
111 *
112 * General:
113 * - Tidy FrameGraphics
114 * - Comment
115 * - UserSettings.DEFAULT_PROFILE_NAME not actually a user setting
116 *
117 * Done:
118 * - Timers
119 * - Timer input animations
120 * - Finish DisplayController/FrameGraphics separation
121 * - Anchor constraints (create class)
122 * - Make MessageBay take lead from DisplayController
123 * - Make message bay display again (to do with above)
124 * - Tooltips (layout broken in Swing as window size not correct when tooltips laid out)
125 * - Frame transitions
126 * - Frame transitions in twin-frames mode drawing over each other
127 * - JFX DnD manager
128 * - Reinstate Simple
129 * - Reinstate exclude source
130 * - Reinstate commented code
131 * - Twin-frames division 0 at startup
132 * - Twin frames off-by-one frame size (draws a line of erroneous pixels)
133 * - MessageBay delays showing messages on start-up
134 * - JFX Antialiasing done on theScene??? Doesn't apply for our situation
135 * - Incorporate Clip class into code
136 * - Enforced clip
137 *
138 * @author cts16
[4]139 * @author jdm18
140 */
[1102]141public class Browser implements SaveStateChangedEventListener {
142
[1460]143 public static final boolean DEBUG = true;
144
[1102]145 public static final Ecosystem ECOSYSTEM_TYPE = Ecosystem.Swing;
[67]146
[139]147 public static Browser _theBrowser = null;
[535]148
149 public static ProxyAuth proxyAuth = new ProxyAuth();
[348]150
[763]151 public static boolean _hasExited = false;
[147]152
[1102]153 /** A flag which is set once the application is exiting. */
[1075]154 protected boolean _isExiting = false;
[67]155
[1075]156 protected static boolean _initComplete = false;
[667]157
[1215]158 private static String _startFrame = null;
[154]159
[1244]160
[4]161 /**
162 * Constructs a new Browser object, then launches it
163 *
164 * @param args
165 */
[1264]166 public static void main(String[] args) {
[1327]167 if (AuthenticatorBrowser.isAuthenticationRequired()) {
[1244]168 String starting_user_name = System.getProperty("user.name");
169 System.setProperty("startinguser.name", starting_user_name);
[1303]170 System.setProperty("user.name", AuthenticatorBrowser.USER_NOBODY);
[1244]171 }
172
[1102]173 // Parse the starting frame command-line argument
[667]174 if(args.length > 0) {
[1215]175 setStartFrame(args[0]);
176 if(!Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
177 setStartFrame(getStartFrame() + "1");
[1170]178 }
[1102]179 } else {
[1215]180 setStartFrame("home1");
[1013]181 }
[1039]182
[1102]183 // Window icon must be set before initialisation
184 GraphicsManager.setWindowIcon(DisplayController.ICON_IMAGE);
185
186 // Setup the GIO ecosystem so it is ready when we need it
187 EcosystemManager.createEcosystem(ECOSYSTEM_TYPE);
188
189 try {
190 EcosystemManager.getMiscManager().runOnGIOThread(new BlockingRunnable() {
191 @Override
192 public void execute() {
193 init();
[1521]194 MessageBay.showDelayedMessages(true);
[1102]195 }
196 });
197 } catch (Throwable e) {
198 e.printStackTrace(System.err);
199 System.exit(1);
200 }
201
[1521]202 //MessageBay.showDelayedMessages(true);
[4]203 }
[1102]204
[1215]205 public static void init() {
[1327]206 if (AuthenticatorBrowser.isAuthenticationRequired()) {
[1215]207 try {
[1282]208 _theBrowser = AuthenticatorBrowser.getInstance();
[1215]209 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | ClassNotFoundException | SQLException e) {
210 e.printStackTrace();
211 }
[1282]212 } else {
213 _theBrowser = new Browser();
214 }
[147]215
[1102]216 EcosystemManager.getGraphicsManager().requestFocus();
217
[1282]218 // Java's way of getting access to internet. Authenticating the user with their proxy username and password.
[1102]219 Authenticator.setDefault(proxyAuth);
220
221 _initComplete = true;
[145]222 }
[348]223
[320]224 /**
[147]225 * @return
[139]226 *
[1215]227 * True if the application is about to exit. False if not. Note that this is
[147]228 * only set once the window is in its closed state (not closing) or if the
[1215]229 * application has explicitly being requested to exit.
[139]230 *
231 * @see Browser#exit()
232 *
233 */
[1102]234 public boolean isExiting() {
[139]235 return _isExiting;
236 }
[67]237
[763]238 public static boolean isInitComplete() {
[154]239 return _initComplete;
240 }
[1281]241
242 protected Browser(String mode) {
243 System.out.println("Running Expeditee in " + mode + " mode.");
244 }
[154]245
[1215]246 protected Browser() {
[67]247 // center the frame on the screen
[1102]248 GraphicsManager g = EcosystemManager.getGraphicsManager();
249 Dimension screen = g.getScreenSize();
250 double xpos = (screen.width - UserSettings.InitialWidth.get()) / 2.0;
251 double ypos = (screen.height - UserSettings.InitialHeight.get()) / 2.0;
252 g.setWindowLocation(new Point((int) xpos, (int) ypos));
[4]253
[1102]254 DisplayController.Init();
[1215]255
[1102]256 DisplayController.addDisplayObserver(WidgetCacheManager.getInstance());
257 if (ECOSYSTEM_TYPE == Ecosystem.Swing) {
258 DisplayController.addDisplayObserver(PopupManager.getInstance());
259 }
[233]260
[1102]261 setInputManagerWindowRoutines();
[1215]262
[699]263 // Reset windows to user specified size
[1102]264 Dimension initialWindowSize = new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get());
265 g.setWindowSize(initialWindowSize);
[1215]266
[781]267 // Load documentation and start pages
268 FrameUtils.extractResources(false);
[1215]269
270 // Load fonts before loading any frames so the items on the frames will be able
271 // to access their fonts
[781]272 Text.InitFonts();
[1215]273
[1329]274 if (!AuthenticatorBrowser.isAuthenticationRequired() && UserSettings.PublicAndPrivateResources) {
[1434]275 String userName = System.getProperty("user.name"); //UserSettings.ProfileName.get();
[1415]276 if (!FrameIO.personalResourcesExist(userName)) {
277 FrameIO.setupPersonalResources(userName);
278 }
[1244]279 }
[1434]280
281 Settings.Init();
282 Frame userProfile = loadInitialProfiles();
283 FrameIO.changeParentAndSubFolders(FrameIO.PARENT_FOLDER);
284
[139]285 // Listen for save status to display during and after runtime
286 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
[1215]287
[4]288 try {
[1102]289 MessageBay.warningMessages(Actions.Init());
[570]290
[667]291 // Go to the start frame if specified, otherwise go to the profile frame
[1215]292 if (getStartFrame() == null) {
293 setStartFrame(UserSettings.StartFrame.get());
294 if (getStartFrame() != null && !Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
295 setStartFrame(getStartFrame() + "1");
[1102]296 }
[732]297 }
[1215]298
[1434]299 Frame start = null;
[1215]300 if ((start = FrameIO.LoadFrame(getStartFrame())) != null) {
301 // Make sure HomeFrame gets set
302 UserSettings.HomeFrame.set(start.getName());
303
304 // Go to the start frame
305 DisplayController.setCurrentFrame(start, true);
306 } else {
307 // If an invalid start frame was specified, show a warning
308 if (getStartFrame() != null) {
309 MessageBay.warningMessage("Unknown frame: " + getStartFrame());
[1170]310 }
[1215]311
312 // Go to the profile frame
313 FrameUtils.loadFirstFrame(userProfile);
314 }
315
[1102]316 DisplayController.updateTitle();
[147]317
[1102]318 // Don't refresh for the profile frame otherwise error messages are shown twice
319 if (!DisplayController.getCurrentFrame().equals(userProfile)) {
320 StandardGestureActions.Refresh();
[1215]321 // If it's the profile frame just reparse it in order to display
322 // images/circles/widgets correctly
[513]323 } else {
[1102]324 FrameUtils.Parse(userProfile);
[513]325 }
[108]326 } catch (Exception e) {
327 e.printStackTrace();
328 Logger.Log(e);
329 }
330 }
[4]331
[1170]332 @Override
[1102]333 public void saveCompleted(SaveStateChangedEvent event)
334 {
335 MessageBay.displayMessage("Save finished!", Colour.BLUE);
[348]336 }
337
[1170]338 @Override
[1102]339 public void saveStarted(SaveStateChangedEvent event)
340 {
[147]341 String name = event.getEntity().getSaveName();
[1170]342 if (name == null) {
343 name = "data";
344 }
[1102]345 MessageBay.displayMessage("Saving " + name + "...", Colour.BLUE);
[139]346 }
[147]347
[139]348 /**
[147]349 * Closes the browser and ends the application. Performs saving operations -
350 * halting until saves have completed. Feedback is given to the user while
351 * the application is exiting. Must call on the swing thread.
[139]352 */
353 public void exit() {
[147]354
[139]355 // Set exiting flag
356 _isExiting = true;
[147]357
[139]358 MessageBay.displayMessage("System exiting...");
[147]359
[139]360 /**
361 * TODO: Prompt the user etc.
362 */
363
[147]364 // TODO: Should we should a popup with a progress bar for user feedback?
365 // this would be nice and easy to do.
[139]366 // Exit on a dedicated thread so that feedback can be obtained
367 new Exiter().start(); // this will exit the application
368 }
[147]369
[139]370 /**
[147]371 * The system must exit on a different thread other than the swing thread so
372 * that the save threads can fire save-feedback to the swing thread and thus
[139]373 * provide user feedback on asynchronous save operations.
374 *
375 * @author Brook Novak
[147]376 *
[139]377 */
378 private class Exiter extends Thread {
379
380 @Override
381 public void run() {
[147]382
[139]383 // The final save point for saveable entities
384 EntitySaveManager.getInstance().saveAll();
385 try {
386 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
387 } catch (InterruptedException e) {
388 e.printStackTrace();
389 }
[1102]390
391 // Stop any agents or simple programs
392 Simple.stop();
393 Actions.stopAgent();
394 // Wait for them to stop
395 try {
396 // Only stop if need to...
397 // Brook: What purpose does this serve?
398 MessageBay.displayMessage("Stopping Simple programs...");
399 while (Simple.isProgramRunning()) {
400 Thread.sleep(100);
401 }
402
403 MessageBay.displayMessage("Stopping Agents...");
404 /* TODO: Only stop if need to... */
405 while (Actions.isAgentRunning()) {
406 Thread.sleep(100); // Brook: What purpose does this serve?
407 }
408 } catch (Exception e) {
[139]409
[1102]410 }
[139]411
[1102]412 MessageBay.displayMessage("Saving current frame...");
413 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
[147]414
[1102]415 MessageBay.displayMessage("Saving stats...");
416 StatsLogger.WriteStatsFile();
[147]417
[1102]418 if (MailSession.getInstance() != null) {
419 if (MailSession.getInstance().finalise()) {
420 // TODO display this message before the finalising
421 // is done but only if the mail needs closing
422 MessageBay.displayMessage("Closed ExpMail...");
423 }
424 }
[139]425
[1102]426 if (FrameShare.getInstance() != null) {
427 MessageBay.displayMessage("Stopping FrameServer...");
428 FrameShare.getInstance().finalise();
429 }
[242]430
[1102]431 MessageBay.displayMessage("System exited");
[147]432
[1102]433 // Finally remove the messages frameset
[1293]434 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH, false);
[298]435
[1102]436 /*
437 * Create a new messages folder so that it doesn't throw
438 * Exceptions when two Expeditee's open at once and the
439 * second tries to save its messages
440 */
441 File file = new File(FrameIO.MESSAGES_PATH + "messages");
442 file.mkdirs();
[311]443
[1102]444 Browser._hasExited = true;
445
446 System.exit(0);
[139]447 }
448 }
449
[242]450 /**
451 * Used to set up the the browser for use in testing.
452 *
453 * @return
454 */
[1102]455 public static Browser initializeForTesting()
456 {
[156]457 if (Browser._theBrowser == null) {
[311]458 FrameShare.disableNetworking = true;
[242]459 MailSession._autoConnect = false;
[311]460
[926]461 Browser.main(new String[]{});
[156]462 try {
463 while (!isInitComplete()) {
464 Thread.sleep(10);
465 }
466 } catch (Exception e) {
467 }
468 }
469 return _theBrowser;
470 }
[1102]471
[1215]472 private static void setInputManagerWindowRoutines() {
[1102]473 InputManager manager = EcosystemManager.getInputManager();
474
475 // Refresh the layout when the window resizes
476 manager.addWindowEventListener(new WindowEventListener() {
477 @Override
478 public void onWindowEvent(WindowEventType type)
479 {
[1170]480 if (type != WindowEventType.WINDOW_RESIZED) {
481 return;
482 }
[1102]483 DisplayController.refreshWindowSize();
484 FrameIO.RefreshCacheImages();
485 for (Frame frame : DisplayController.getFrames()) {
486 if (frame != null) {
487 ItemUtils.Justify(frame);
488 frame.refreshSize();
489 }
490 }
491 DisplayController.requestRefresh(false);
492 }
493 });
494
495 manager.addWindowEventListener(new WindowEventListener() {
496 @Override
497 public void onWindowEvent(WindowEventType type)
498 {
[1170]499 if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
500 return;
501 }
[1102]502 StandardGestureActions.mouseExitedWindow();
503 }
504 });
505
506 manager.addWindowEventListener(new WindowEventListener() {
507 @Override
508 public void onWindowEvent(WindowEventType type)
509 {
[1170]510 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
511 return;
512 }
[1102]513 StandardGestureActions.mouseEnteredWindow();
514 }
515 });
516
517 manager.addWindowEventListener(new WindowEventListener() {
518 @Override
519 public void onWindowEvent(WindowEventType type)
520 {
[1170]521 if (type != WindowEventType.WINDOW_CLOSED) {
522 return;
523 }
524 if (Browser._theBrowser != null) {
525 Browser._theBrowser.exit();
526 }
[1102]527 }
528 });
529 }
[156]530
[1102]531 /**
532 * @return The user's profile frame.
533 */
[1434]534 public static Frame loadInitialProfiles()
[1102]535 {
536 String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
[1434]537 String userName = System.getProperty("user.name");
[1102]538
[1434]539 Frame defaultProfile = loadProfile(defaultProfileName);
[1141]540 Frame userProfile = loadProfile(userName);
541
[1102]542 MessageBay.warningMessages(FrameUtils.ParseProfile(defaultProfile));
543
544 // Save the cursor if the defaultProfile had a custom cursor
545 Collection<Item> cursor = null;
546 if(FreeItems.hasCursor()) {
547 cursor = new ArrayList<Item>();
548 cursor.addAll(FreeItems.getCursor());
549 }
550
[1434]551 //MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
[1102]552
[1170]553 if (cursor != null && !FreeItems.hasCursor()) {
554 FreeItems.setCursor(cursor);
555 }
[1102]556
[1434]557 FrameUtils.ParseProfile(userProfile);
[1470]558 String proFileName = userProfile.getFramesetName();
559 UserSettings.ProfileName.set(proFileName);
[1434]560
[1102]561 return userProfile;
562 }
563
[1242]564 protected static Frame loadProfile(String userName) {
[1102]565 Frame profile = FrameIO.LoadProfile(userName);
566
567 if (profile == null) {
568 try {
[1270]569 profile = FrameIO.CreateNewProfile(userName, null, null);
[1102]570 } catch (Exception e) {
571 // TODO tell the user that there was a problem creating the
572 // profile frame and close nicely
573 e.printStackTrace();
574 assert (false);
575 }
576 }
577 return profile;
578 }
[1215]579
580 public static String getStartFrame() {
581 return _startFrame;
582 }
583
584 public static void setStartFrame(String _startFrame) {
585 Browser._startFrame = _startFrame;
586 }
[4]587}
Note: See TracBrowser for help on using the repository browser.