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

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File size: 16.0 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 MessageBay.showDelayedMessages();
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 Frame userProfile = loadProfiles();
246
247 // Listen for save status to display during and after runtime
248 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
249
250 try {
251 MessageBay.warningMessages(Actions.Init());
252
253 Settings.Init();
254
255 // Go to the start frame if specified, otherwise go to the profile frame
256 Frame start = null;
257 if(_startFrame == null) {
258 _startFrame = UserSettings.StartFrame.get();
259 if(_startFrame != null && !Character.isDigit(_startFrame.charAt(_startFrame.length() - 1))) {
260 _startFrame = _startFrame + "1";
261 }
262 }
263
264 if((start = FrameIO.LoadFrame(_startFrame)) != null) {
265 // Make sure HomeFrame gets set
266 UserSettings.HomeFrame.set(start.getName());
267
268 // Go to the start frame
269 DisplayController.setCurrentFrame(start, true);
270 } else {
271 // If an invalid start frame was specified, show a warning
272 if(_startFrame != null) MessageBay.warningMessage("Unknown frame: " + _startFrame);
273
274 // Go to the profile frame
275 FrameUtils.loadFirstFrame(userProfile);
276 }
277
278 DisplayController.updateTitle();
279
280 // Don't refresh for the profile frame otherwise error messages are shown twice
281 if (!DisplayController.getCurrentFrame().equals(userProfile)) {
282 StandardGestureActions.Refresh();
283 // If it's the profile frame just reparse it in order to display images/circles/widgets correctly
284 } else {
285 FrameUtils.Parse(userProfile);
286 }
287 } catch (Exception e) {
288 e.printStackTrace();
289 Logger.Log(e);
290 }
291 }
292
293 public void saveCompleted(SaveStateChangedEvent event)
294 {
295 MessageBay.displayMessage("Save finished!", Colour.BLUE);
296 }
297
298 public void saveStarted(SaveStateChangedEvent event)
299 {
300 String name = event.getEntity().getSaveName();
301 if (name == null) name = "data";
302 MessageBay.displayMessage("Saving " + name + "...", Colour.BLUE);
303 }
304
305 /**
306 * Closes the browser and ends the application. Performs saving operations -
307 * halting until saves have completed. Feedback is given to the user while
308 * the application is exiting. Must call on the swing thread.
309 */
310 public void exit() {
311
312 // Set exiting flag
313 _isExiting = true;
314
315 MessageBay.displayMessage("System exiting...");
316
317 /**
318 * TODO: Prompt the user etc.
319 */
320
321 // TODO: Should we should a popup with a progress bar for user feedback?
322 // this would be nice and easy to do.
323 // Exit on a dedicated thread so that feedback can be obtained
324 new Exiter().start(); // this will exit the application
325 }
326
327 /**
328 * The system must exit on a different thread other than the swing thread so
329 * that the save threads can fire save-feedback to the swing thread and thus
330 * provide user feedback on asynchronous save operations.
331 *
332 * @author Brook Novak
333 *
334 */
335 private class Exiter extends Thread {
336
337 @Override
338 public void run() {
339
340 // The final save point for saveable entities
341 EntitySaveManager.getInstance().saveAll();
342 try {
343 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
344 } catch (InterruptedException e) {
345 e.printStackTrace();
346 }
347
348 // Stop any agents or simple programs
349 Simple.stop();
350 Actions.stopAgent();
351 // Wait for them to stop
352 try {
353 // Only stop if need to...
354 // Brook: What purpose does this serve?
355 MessageBay.displayMessage("Stopping Simple programs...");
356 while (Simple.isProgramRunning()) {
357 Thread.sleep(100);
358 }
359
360 MessageBay.displayMessage("Stopping Agents...");
361 /* TODO: Only stop if need to... */
362 while (Actions.isAgentRunning()) {
363 Thread.sleep(100); // Brook: What purpose does this serve?
364 }
365 } catch (Exception e) {
366
367 }
368
369 MessageBay.displayMessage("Saving current frame...");
370 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
371
372 MessageBay.displayMessage("Saving stats...");
373 StatsLogger.WriteStatsFile();
374
375 if (MailSession.getInstance() != null) {
376 if (MailSession.getInstance().finalise()) {
377 // TODO display this message before the finalising
378 // is done but only if the mail needs closing
379 MessageBay.displayMessage("Closed ExpMail...");
380 }
381 }
382
383 if (FrameShare.getInstance() != null) {
384 MessageBay.displayMessage("Stopping FrameServer...");
385 FrameShare.getInstance().finalise();
386 }
387
388 MessageBay.displayMessage("System exited");
389
390 // Finally remove the messages frameset
391 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH);
392
393 /*
394 * Create a new messages folder so that it doesn't throw
395 * Exceptions when two Expeditee's open at once and the
396 * second tries to save its messages
397 */
398 File file = new File(FrameIO.MESSAGES_PATH + "messages");
399 file.mkdirs();
400
401 Browser._hasExited = true;
402
403 System.exit(0);
404 }
405 }
406
407 /**
408 * Used to set up the the browser for use in testing.
409 *
410 * @return
411 */
412 public static Browser initializeForTesting()
413 {
414 if (Browser._theBrowser == null) {
415 FrameShare.disableNetworking = true;
416 MailSession._autoConnect = false;
417
418 Browser.main(new String[]{});
419 try {
420 while (!isInitComplete()) {
421 Thread.sleep(10);
422 }
423 } catch (Exception e) {
424 }
425 }
426 return _theBrowser;
427 }
428
429 private static void setInputManagerWindowRoutines()
430 {
431 InputManager manager = EcosystemManager.getInputManager();
432
433 // Refresh the layout when the window resizes
434 manager.addWindowEventListener(new WindowEventListener() {
435 @Override
436 public void onWindowEvent(WindowEventType type)
437 {
438 if (type != WindowEventType.WINDOW_RESIZED) return;
439 DisplayController.refreshWindowSize();
440 FrameIO.RefreshCacheImages();
441 for (Frame frame : DisplayController.getFrames()) {
442 if (frame != null) {
443 ItemUtils.Justify(frame);
444 frame.refreshSize();
445 }
446 }
447 DisplayController.requestRefresh(false);
448 }
449 });
450
451 manager.addWindowEventListener(new WindowEventListener() {
452 @Override
453 public void onWindowEvent(WindowEventType type)
454 {
455 if (type != WindowEventType.MOUSE_EXITED_WINDOW) return;
456 StandardGestureActions.mouseExitedWindow();
457 }
458 });
459
460 manager.addWindowEventListener(new WindowEventListener() {
461 @Override
462 public void onWindowEvent(WindowEventType type)
463 {
464 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) return;
465 StandardGestureActions.mouseEnteredWindow();
466 }
467 });
468
469 manager.addWindowEventListener(new WindowEventListener() {
470 @Override
471 public void onWindowEvent(WindowEventType type)
472 {
473 if (type != WindowEventType.WINDOW_CLOSED) return;
474 if (Browser._theBrowser != null) Browser._theBrowser.exit();
475 }
476 });
477 }
478
479 /**
480 * @return The user's profile frame.
481 */
482 private static Frame loadProfiles()
483 {
484 String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
485 String userName = UserSettings.ProfileName.get();
486
487 Frame defaultProfile = loadProfile(defaultProfileName);
488 Frame userProfile = loadProfile(userName);
489
490 MessageBay.warningMessages(FrameUtils.ParseProfile(defaultProfile));
491
492 // Save the cursor if the defaultProfile had a custom cursor
493 Collection<Item> cursor = null;
494 if(FreeItems.hasCursor()) {
495 cursor = new ArrayList<Item>();
496 cursor.addAll(FreeItems.getCursor());
497 }
498
499 MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
500
501 if (cursor != null && !FreeItems.hasCursor()) FreeItems.setCursor(cursor);
502
503 return userProfile;
504 }
505
506 protected static Frame loadProfile(String userName)
507 {
508 Frame profile = FrameIO.LoadProfile(userName);
509
510 if (profile == null) {
511 try {
512 profile = FrameIO.CreateNewProfile(userName);
513 } catch (Exception e) {
514 // TODO tell the user that there was a problem creating the
515 // profile frame and close nicely
516 e.printStackTrace();
517 assert (false);
518 }
519 }
520 return profile;
521 }
522}
Note: See TracBrowser for help on using the repository browser.