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

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

Updated the authentication and multiuser framesets to have the appropriate ownerships and permissions.

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