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

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

org.expeditee.gui.Browser ->
org.expeditee.items.widgets.JfxBrowser ->
Fixed exception that was sometimes happening when Expeditee was closed after a JfxBrowser had been in use. Issue was that JfxBrowser runs operations on JFXThread which needed to be shut down nicely. Sometimes when Expeditee gets closed, and the JfxBrowser had something run in another thread, an exception would be caused when the JFXThread got terminated unexpectedly while running something. The solution was to detect if the JfxBrowser ever gets used, and if it is used then Expeditee must nicely close JFX thread as part of its shut down process.

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