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

Last change on this file since 1141 was 1141, checked in by bln4, 6 years ago

org.expeditee.gui.Browser ->

Fixed some issues with ordering of execution that caused problems when no profiles were already present on startup. For future reference; Settings.Init() can be called prior to loadProfiles(). User profile must be loaded in prior to default profile.

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