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

Last change on this file since 1552 was 1552, checked in by bnemhaus, 3 years ago

Added method specifically for displaying debug messages (Browser.debugMessage). All new debug messages should use this method so that we can easily disable these by switching a single variable.

File size: 19.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 = getExpediteeUserName();
172 System.setProperty("startinguser.name", starting_user_name);
173 setExpediteeUserName(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 = getExpediteeUserName(); //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 private static String expediteeUserName = System.getProperty("user.name");
541
542 public static String getExpediteeUserName() {
543// String expediteeUserNameLocal = FrameIO.ConvertToValidFramesetName(expediteeUserName);
544// return expediteeUserNameLocal;
545 return expediteeUserName;
546 }
547
548 public static void setExpediteeUserName(String username) {
549 expediteeUserName = username;
550 }
551
552 public static String getExpediteeUserProfileName() {
553 String expediteeUserNameLocal = FrameIO.ConvertToValidFramesetName(expediteeUserName);
554 return expediteeUserNameLocal;
555 }
556
557 /**
558 * @return The user's profile frame.
559 */
560 public static Frame loadInitialProfiles() {
561 String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
562 String userName = getExpediteeUserName();
563 String userProfileName = getExpediteeUserProfileName();
564
565 Frame defaultProfile = loadProfile(defaultProfileName, defaultProfileName);
566 Frame userProfile = loadProfile(userName, userProfileName);
567
568 MessageBay.warningMessages(FrameUtils.ParseProfile(defaultProfile));
569
570 // Save the cursor if the defaultProfile had a custom cursor
571 Collection<Item> cursor = null;
572 if(FreeItems.hasCursor()) {
573 cursor = new ArrayList<Item>();
574 cursor.addAll(FreeItems.getCursor());
575 }
576
577 //MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
578
579 if (cursor != null && !FreeItems.hasCursor()) {
580 FreeItems.setCursor(cursor);
581 }
582
583 FrameUtils.ParseProfile(userProfile);
584 String proFileName = userProfile.getFramesetName();
585 UserSettings.ProfileName.set(proFileName);
586
587 return userProfile;
588 }
589
590 protected static Frame loadProfile(String username, String profileName) {
591 Frame profile = FrameIO.LoadProfile(profileName);
592
593 if (profile == null) {
594 try {
595 profile = FrameIO.CreateNewProfile(username, profileName, null, null);
596 } catch (Exception e) {
597 // TODO tell the user that there was a problem creating the
598 // profile frame and close nicely
599 e.printStackTrace();
600 assert (false);
601 }
602 }
603 return profile;
604 }
605
606 public static String getStartFrame() {
607 return _startFrame;
608 }
609
610 public static void setStartFrame(String _startFrame) {
611 Browser._startFrame = _startFrame;
612 }
613
614 public static void debugMessage(String className, String methodName, String... message) {
615 if (!Browser.DEBUG) {
616 return;
617 }
618
619 System.err.print(className + "::" + methodName);
620 if (message.length == 1) {
621 System.err.println("::" + message[0]);
622 } else {
623 System.err.println();
624 for (String m: message) {
625 System.err.println("\t" + m);
626 }
627 }
628 }
629}
Note: See TracBrowser for help on using the repository browser.