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

Last change on this file since 1281 was 1281, checked in by bln4, 5 years ago

Implemented band-aid to fix authentication mode not saving last edits on exit (and I imagine other problems).

The issue was that Browser._theBrowser was not being set because the Browser.init method was being highjacked by Authenticator. Authenticator now extends Browser, calls a newly minted (mostly) empty super constructor and sets Browser._theBrowser to itself. Once commits concerning how Expeditee startup has been made this can be tidied up.

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