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

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

Implementation of ProfileManager. Refactor + additional content for how new profiles are created. The refactoring split out the creation of the default profile from user profiles. Refactoring revealed a long term bug that was causing user profiles to generate with incorrect information. The additional content fixed this bug by introducing the ${USER.NAME} variable, so that the default profile frameset can specify resource locations located in the users resource directory.

org.expeditee.auth.AuthenticatorBrowser
org.expeditee.auth.account.Create
org.expeditee.gui.Browser
org.expeditee.gui.management.ProfileManager
org.expeditee.setting.DirectoryListSetting
org.expeditee.setting.ListSetting
org.expeditee.settings.UserSettings

Implementation of ResourceManager as a core location to get resources from the file system. Also the additional variable ${CURRENT_FRAMESET} to represent the current frameset, so that images can be stored in the directory of the current frameset. This increases portability of framesets.

org.expeditee.gui.FrameIO
org.expeditee.gui.management.ResourceManager
org.expeditee.gui.management.ResourceUtil
Audio:

#NB: Audio used to only operate on a single directory. This has been updated to work in a same way as images. That is: when you ask for a specific resouce, it looks to the user settings to find a sequence of directories to look at in order until it manages to find the desired resource.


There is still need however for a single(ish) source of truth for the .banks and .mastermix file. Therefore these files are now always located in resource-<username>\audio.
org.apollo.agents.MelodySearch
org.apollo.audio.structure.AudioStructureModel
org.apollo.audio.util.MultiTrackPlaybackController
org.apollo.audio.util.SoundDesk
org.apollo.gui.FrameLayoutDaemon
org.apollo.io.AudioPathManager
org.apollo.util.AudioPurger
org.apollo.widgets.FramePlayer
org.apollo.widgets.SampledTrack

Images:

org.expeditee.items.ItemUtils

Frames:

org.expeditee.gui.FrameIO

Fixed a error in the FramePlayer class caused by an incorrect use of toArray().

org.apollo.widgets.FramePlayer


Added several short cut keys to allow for the Play/Pause (Ctrl + P), mute (Ctrl + M) and volume up/down (Ctrl + +/-) when hovering over SampledTrack widgets.

org.apollo.widgets.SampledTrack


Changed the way that Authenticate.login parses the new users profile to be more consistance with other similar places in code.

org.expeditee.auth.account.Authenticate


Encapsulated _body, _surrogateItemsBody and _primaryItemsBody in Frame class. Also changed getBody function to take a boolean flag as to if it should respect the current surrogate mode. If it should then it makes sure that labels have not changed since last time getBody was called.

org.expeditee.gui.Frame

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