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

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

Fixed bug causing Expeditee to sometimes never finish initialising. This was caused by a refresh being requested by a refresh process. A lock had been held on a list which meant we got deadlock. This was fixed by realising that Browser.main can ask the messagebay to display delayed messages once the previous blocking runnable is finished running. There was not need to delay this until after a subsequent refresh.

File size: 17.9 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.WidgetCacheManager;
51import org.expeditee.network.FrameShare;
52import org.expeditee.settings.Settings;
53import org.expeditee.settings.UserSettings;
54import org.expeditee.stats.Logger;
55import org.expeditee.stats.StatsLogger;
56import org.expeditee.taskmanagement.EntitySaveManager;
57import org.expeditee.taskmanagement.SaveStateChangedEvent;
58import org.expeditee.taskmanagement.SaveStateChangedEventListener;
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 *
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
139 * @author jdm18
140 */
141public class Browser implements SaveStateChangedEventListener {
142
143 public static final boolean DEBUG = true;
144
145 public static final Ecosystem ECOSYSTEM_TYPE = Ecosystem.Swing;
146
147 public static Browser _theBrowser = null;
148
149 public static ProxyAuth proxyAuth = new ProxyAuth();
150
151 public static boolean _hasExited = false;
152
153 /** A flag which is set once the application is exiting. */
154 protected boolean _isExiting = false;
155
156 protected static boolean _initComplete = false;
157
158 private static String _startFrame = null;
159
160
161 /**
162 * Constructs a new Browser object, then launches it
163 *
164 * @param args
165 */
166 public static void main(String[] args) {
167 if (AuthenticatorBrowser.isAuthenticationRequired()) {
168 String starting_user_name = System.getProperty("user.name");
169 System.setProperty("startinguser.name", starting_user_name);
170 System.setProperty("user.name", AuthenticatorBrowser.USER_NOBODY);
171 }
172
173 // Parse the starting frame command-line argument
174 if(args.length > 0) {
175 setStartFrame(args[0]);
176 if(!Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
177 setStartFrame(getStartFrame() + "1");
178 }
179 } else {
180 setStartFrame("home1");
181 }
182
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();
194 }
195 });
196 } catch (Throwable e) {
197 e.printStackTrace(System.err);
198 System.exit(1);
199 }
200
201 MessageBay.showDelayedMessages(true);
202 }
203
204 public static void init() {
205 if (AuthenticatorBrowser.isAuthenticationRequired()) {
206 try {
207 _theBrowser = AuthenticatorBrowser.getInstance();
208 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | ClassNotFoundException | SQLException e) {
209 e.printStackTrace();
210 }
211 } else {
212 _theBrowser = new Browser();
213 }
214
215 EcosystemManager.getGraphicsManager().requestFocus();
216
217 // Java's way of getting access to internet. Authenticating the user with their proxy username and password.
218 Authenticator.setDefault(proxyAuth);
219
220 _initComplete = true;
221 }
222
223 /**
224 * @return
225 *
226 * True if the application is about to exit. False if not. Note that this is
227 * only set once the window is in its closed state (not closing) or if the
228 * application has explicitly being requested to exit.
229 *
230 * @see Browser#exit()
231 *
232 */
233 public boolean isExiting() {
234 return _isExiting;
235 }
236
237 public static boolean isInitComplete() {
238 return _initComplete;
239 }
240
241 protected Browser(String mode) {
242 System.out.println("Running Expeditee in " + mode + " mode.");
243 }
244
245 protected Browser() {
246 // center the frame on the screen
247 GraphicsManager g = EcosystemManager.getGraphicsManager();
248 Dimension screen = g.getScreenSize();
249 double xpos = (screen.width - UserSettings.InitialWidth.get()) / 2.0;
250 double ypos = (screen.height - UserSettings.InitialHeight.get()) / 2.0;
251 g.setWindowLocation(new Point((int) xpos, (int) ypos));
252
253 DisplayController.Init();
254
255 DisplayController.addDisplayObserver(WidgetCacheManager.getInstance());
256 if (ECOSYSTEM_TYPE == Ecosystem.Swing) {
257 DisplayController.addDisplayObserver(PopupManager.getInstance());
258 }
259
260 setInputManagerWindowRoutines();
261
262 // Reset windows to user specified size
263 Dimension initialWindowSize = new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get());
264 g.setWindowSize(initialWindowSize);
265
266 // Load documentation and start pages
267 FrameUtils.extractResources(false);
268
269 // Load fonts before loading any frames so the items on the frames will be able
270 // to access their fonts
271 Text.InitFonts();
272
273 if (!AuthenticatorBrowser.isAuthenticationRequired() && UserSettings.PublicAndPrivateResources) {
274 String userName = System.getProperty("user.name"); //UserSettings.ProfileName.get();
275 if (!FrameIO.personalResourcesExist(userName)) {
276 FrameIO.setupPersonalResources(userName);
277 }
278 }
279
280 Settings.Init();
281 Frame userProfile = loadInitialProfiles();
282 FrameIO.changeParentAndSubFolders(FrameIO.PARENT_FOLDER);
283
284 // Listen for save status to display during and after runtime
285 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
286
287 try {
288 MessageBay.warningMessages(Actions.Init());
289
290 // Go to the start frame if specified, otherwise go to the profile frame
291 if (getStartFrame() == null) {
292 setStartFrame(UserSettings.StartFrame.get());
293 if (getStartFrame() != null && !Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
294 setStartFrame(getStartFrame() + "1");
295 }
296 }
297
298 Frame start = null;
299 if ((start = FrameIO.LoadFrame(getStartFrame())) != null) {
300 // Make sure HomeFrame gets set
301 UserSettings.HomeFrame.set(start.getName());
302
303 // Go to the start frame
304 DisplayController.setCurrentFrame(start, true);
305 } else {
306 // If an invalid start frame was specified, show a warning
307 if (getStartFrame() != null) {
308 MessageBay.warningMessage("Unknown frame: " + getStartFrame());
309 }
310
311 // Go to the profile frame
312 FrameUtils.loadFirstFrame(userProfile);
313 }
314
315 DisplayController.updateTitle();
316
317 // Don't refresh for the profile frame otherwise error messages are shown twice
318 if (!DisplayController.getCurrentFrame().equals(userProfile)) {
319 StandardGestureActions.Refresh();
320 // If it's the profile frame just reparse it in order to display
321 // images/circles/widgets correctly
322 } else {
323 FrameUtils.Parse(userProfile);
324 }
325 } catch (Exception e) {
326 e.printStackTrace();
327 Logger.Log(e);
328 }
329 }
330
331 @Override
332 public void saveCompleted(SaveStateChangedEvent event)
333 {
334 MessageBay.displayMessage("Save finished!", Colour.BLUE);
335 }
336
337 @Override
338 public void saveStarted(SaveStateChangedEvent event)
339 {
340 String name = event.getEntity().getSaveName();
341 if (name == null) {
342 name = "data";
343 }
344 MessageBay.displayMessage("Saving " + name + "...", Colour.BLUE);
345 }
346
347 /**
348 * Closes the browser and ends the application. Performs saving operations -
349 * halting until saves have completed. Feedback is given to the user while
350 * the application is exiting. Must call on the swing thread.
351 */
352 public void exit() {
353
354 // Set exiting flag
355 _isExiting = true;
356
357 MessageBay.displayMessage("System exiting...");
358
359 /**
360 * TODO: Prompt the user etc.
361 */
362
363 // TODO: Should we should a popup with a progress bar for user feedback?
364 // this would be nice and easy to do.
365 // Exit on a dedicated thread so that feedback can be obtained
366 new Exiter().start(); // this will exit the application
367 }
368
369 /**
370 * The system must exit on a different thread other than the swing thread so
371 * that the save threads can fire save-feedback to the swing thread and thus
372 * provide user feedback on asynchronous save operations.
373 *
374 * @author Brook Novak
375 *
376 */
377 private class Exiter extends Thread {
378
379 @Override
380 public void run() {
381
382 // The final save point for saveable entities
383 EntitySaveManager.getInstance().saveAll();
384 try {
385 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
386 } catch (InterruptedException e) {
387 e.printStackTrace();
388 }
389
390 // Stop any agents or simple programs
391 Simple.stop();
392 Actions.stopAgent();
393 // Wait for them to stop
394 try {
395 // Only stop if need to...
396 // Brook: What purpose does this serve?
397 MessageBay.displayMessage("Stopping Simple programs...");
398 while (Simple.isProgramRunning()) {
399 Thread.sleep(100);
400 }
401
402 MessageBay.displayMessage("Stopping Agents...");
403 /* TODO: Only stop if need to... */
404 while (Actions.isAgentRunning()) {
405 Thread.sleep(100); // Brook: What purpose does this serve?
406 }
407 } catch (Exception e) {
408
409 }
410
411 MessageBay.displayMessage("Saving current frame...");
412 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
413
414 MessageBay.displayMessage("Saving stats...");
415 StatsLogger.WriteStatsFile();
416
417 if (MailSession.getInstance() != null) {
418 if (MailSession.getInstance().finalise()) {
419 // TODO display this message before the finalising
420 // is done but only if the mail needs closing
421 MessageBay.displayMessage("Closed ExpMail...");
422 }
423 }
424
425 if (FrameShare.getInstance() != null) {
426 MessageBay.displayMessage("Stopping FrameServer...");
427 FrameShare.getInstance().finalise();
428 }
429
430 MessageBay.displayMessage("System exited");
431
432 // Finally remove the messages frameset
433 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH, false);
434
435 /*
436 * Create a new messages folder so that it doesn't throw
437 * Exceptions when two Expeditee's open at once and the
438 * second tries to save its messages
439 */
440 File file = new File(FrameIO.MESSAGES_PATH + "messages");
441 file.mkdirs();
442
443 Browser._hasExited = true;
444
445 System.exit(0);
446 }
447 }
448
449 /**
450 * Used to set up the the browser for use in testing.
451 *
452 * @return
453 */
454 public static Browser initializeForTesting()
455 {
456 if (Browser._theBrowser == null) {
457 FrameShare.disableNetworking = true;
458 MailSession._autoConnect = false;
459
460 Browser.main(new String[]{});
461 try {
462 while (!isInitComplete()) {
463 Thread.sleep(10);
464 }
465 } catch (Exception e) {
466 }
467 }
468 return _theBrowser;
469 }
470
471 private static void setInputManagerWindowRoutines() {
472 InputManager manager = EcosystemManager.getInputManager();
473
474 // Refresh the layout when the window resizes
475 manager.addWindowEventListener(new WindowEventListener() {
476 @Override
477 public void onWindowEvent(WindowEventType type)
478 {
479 if (type != WindowEventType.WINDOW_RESIZED) {
480 return;
481 }
482 DisplayController.refreshWindowSize();
483 FrameIO.RefreshCacheImages();
484 for (Frame frame : DisplayController.getFrames()) {
485 if (frame != null) {
486 ItemUtils.Justify(frame);
487 frame.refreshSize();
488 }
489 }
490 DisplayController.requestRefresh(false);
491 }
492 });
493
494 manager.addWindowEventListener(new WindowEventListener() {
495 @Override
496 public void onWindowEvent(WindowEventType type)
497 {
498 if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
499 return;
500 }
501 StandardGestureActions.mouseExitedWindow();
502 }
503 });
504
505 manager.addWindowEventListener(new WindowEventListener() {
506 @Override
507 public void onWindowEvent(WindowEventType type)
508 {
509 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
510 return;
511 }
512 StandardGestureActions.mouseEnteredWindow();
513 }
514 });
515
516 manager.addWindowEventListener(new WindowEventListener() {
517 @Override
518 public void onWindowEvent(WindowEventType type)
519 {
520 if (type != WindowEventType.WINDOW_CLOSED) {
521 return;
522 }
523 if (Browser._theBrowser != null) {
524 Browser._theBrowser.exit();
525 }
526 }
527 });
528 }
529
530 /**
531 * @return The user's profile frame.
532 */
533 public static Frame loadInitialProfiles()
534 {
535 String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
536 String userName = System.getProperty("user.name");
537
538 Frame defaultProfile = loadProfile(defaultProfileName);
539 Frame userProfile = loadProfile(userName);
540
541 MessageBay.warningMessages(FrameUtils.ParseProfile(defaultProfile));
542
543 // Save the cursor if the defaultProfile had a custom cursor
544 Collection<Item> cursor = null;
545 if(FreeItems.hasCursor()) {
546 cursor = new ArrayList<Item>();
547 cursor.addAll(FreeItems.getCursor());
548 }
549
550 //MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
551
552 if (cursor != null && !FreeItems.hasCursor()) {
553 FreeItems.setCursor(cursor);
554 }
555
556 FrameUtils.ParseProfile(userProfile);
557 String proFileName = userProfile.getFramesetName();
558 UserSettings.ProfileName.set(proFileName);
559
560 return userProfile;
561 }
562
563 protected static Frame loadProfile(String userName) {
564 Frame profile = FrameIO.LoadProfile(userName);
565
566 if (profile == null) {
567 try {
568 profile = FrameIO.CreateNewProfile(userName, null, null);
569 } catch (Exception e) {
570 // TODO tell the user that there was a problem creating the
571 // profile frame and close nicely
572 e.printStackTrace();
573 assert (false);
574 }
575 }
576 return profile;
577 }
578
579 public static String getStartFrame() {
580 return _startFrame;
581 }
582
583 public static void setStartFrame(String _startFrame) {
584 Browser._startFrame = _startFrame;
585 }
586}
Note: See TracBrowser for help on using the repository browser.