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

Last change on this file since 1039 was 1039, checked in by davidb, 8 years ago

Main Addition: full-screen audience mode. Also some whitespace and comment tidy-up.

File size: 17.9 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.awt.Color;
22import java.awt.Dimension;
23import java.awt.Graphics;
24import java.awt.Graphics2D;
25import java.awt.Image;
26import java.awt.MouseInfo;
27import java.awt.Point;
28import java.awt.RenderingHints;
29import java.awt.Toolkit;
30import java.awt.event.ComponentEvent;
31import java.awt.event.ComponentListener;
32import java.awt.event.WindowEvent;
33import java.awt.event.WindowListener;
34import java.awt.event.WindowStateListener;
35import java.io.File;
36import java.net.Authenticator;
37import java.net.URL;
38import java.util.ArrayList;
39import java.util.Collection;
40import java.util.LinkedList;
41
42import javax.swing.JFrame;
43import javax.swing.RepaintManager;
44import javax.swing.SwingUtilities;
45
46import org.expeditee.AbsoluteLayout;
47import org.expeditee.actions.Actions;
48import org.expeditee.actions.Simple;
49import org.expeditee.agents.mail.MailSession;
50import org.expeditee.importer.FrameDNDTransferHandler;
51import org.expeditee.io.ProxyAuth;
52import org.expeditee.items.Item;
53import org.expeditee.items.Text;
54import org.expeditee.items.widgets.WidgetCacheManager;
55import org.expeditee.network.FrameShare;
56import org.expeditee.settings.Settings;
57import org.expeditee.settings.UserSettings;
58import org.expeditee.stats.Logger;
59import org.expeditee.stats.StatsLogger;
60import org.expeditee.taskmanagement.EntitySaveManager;
61import org.expeditee.taskmanagement.SaveStateChangedEvent;
62import org.expeditee.taskmanagement.SaveStateChangedEventListener;
63
64/**
65 * The Main GUI class, comprises what people will see on the screen.<br>
66 * Note: Each Object (Item) is responsible for drawing itself on the screen.<br>
67 * Note2: The Frame is registered as a MouseListener and KeyListener, and
68 * processes any Events.
69 *
70 * @author jdm18
71 *
72 */
73public class Browser extends JFrame implements ComponentListener,
74 WindowListener, WindowStateListener, SaveStateChangedEventListener {
75
76 /**
77 * Default version - just to stop eclipse from complaining about it.
78 */
79 private static final long serialVersionUID = 1L;
80
81 // private static final JScrollPane scrollPane = new JScrollPane();
82
83 public static Browser _theBrowser = null;
84
85 public static ProxyAuth proxyAuth = new ProxyAuth();
86
87 public static boolean _hasExited = false;
88
89 private MouseEventRouter _mouseEventRouter;
90
91 // A flag which is set once the application is exiting.
92 private boolean _isExiting = false;
93
94 private boolean _minimum_version6 = false;
95
96 public boolean isMinimumVersion6() {
97 return _minimum_version6;
98 }
99
100 private static boolean _initComplete = false;
101
102 private static String _startFrame = null;
103
104 /**
105 * Constructs a new Browser object, then launches it
106 *
107 * @param args
108 */
109 public static void main(String[] args) {
110
111 if(args.length > 0) {
112 _startFrame = args[0];
113 if(! Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
114 _startFrame = _startFrame + "1";
115 }
116 else {
117 _startFrame = "home1";
118 }
119
120 // Prepare all expeditee and swing data on the AWT event thread.
121 SwingUtilities.invokeLater(new Runnable() {
122 public void run() {
123 // MessageBay.supressMessages(true);
124
125 // MessageBay.supressMessages(false);
126
127 _theBrowser = new Browser();
128
129 DisplayIO.refreshCursor();
130 _theBrowser.requestFocus();
131 FrameMouseActions.MouseX = MouseInfo.getPointerInfo()
132 .getLocation().x
133 - _theBrowser.getOrigin().x;
134 FrameMouseActions.MouseY = MouseInfo.getPointerInfo()
135 .getLocation().y
136 - _theBrowser.getOrigin().y;
137 _initComplete = true;
138
139 Authenticator.setDefault(proxyAuth);
140 }
141 });
142
143 }
144
145 public Point getOrigin() {
146 return getContentPane().getLocationOnScreen();
147 }
148
149 /**
150 * @return The mouse event router used for this browser. Never null after
151 * browser constructed.
152 */
153 public MouseEventRouter getMouseEventRouter() {
154 return _mouseEventRouter;
155 }
156
157 /**
158 * @return
159 *
160 * True if the application is about to exit. False if not. Not that this is
161 * only set once the window is in its closed state (not closing) or if the
162 * application has explicity being requested to exit.
163 *
164 * @see Browser#exit()
165 *
166 */
167 public boolean isExisting() {
168 return _isExiting;
169 }
170
171 public static boolean isInitComplete() {
172 return _initComplete;
173 }
174
175 public void setSizes(Dimension size) {
176 setSize(size);
177 setPreferredSize(size);
178 Dimension paneSize = getContentPane().getSize();
179 FrameGraphics.setMaxSize(paneSize);
180 }
181
182 private Browser() {
183 // center the frame on the screen
184 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
185 double xpos = screen.getWidth() / 2;
186 double ypos = screen.getHeight() / 2;
187 setLocation((int) (xpos - (UserSettings.InitialWidth.get() / 2)),
188 (int) (ypos - (UserSettings.InitialHeight.get() / 2)));
189
190 addWindowListener(this);
191 addWindowStateListener(this);
192
193 DisplayIO.addDisplayIOObserver(WidgetCacheManager.getInstance());
194 DisplayIO.addDisplayIOObserver(PopupManager.getInstance());
195
196
197 // set up the image used for the icon
198 try
199 {
200 URL iconURL = ClassLoader.getSystemResource("org/expeditee/assets/icons/expediteeicon128.png");
201 if (iconURL != null)
202 {
203 Image localImage = Toolkit.getDefaultToolkit().getImage(iconURL);
204 this.setIconImage(localImage);
205 }
206 }
207 catch (Exception e)
208 {
209 e.printStackTrace();
210 }
211
212 setSizes(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
213
214 // set the layout to absolute layout for widgets
215 this.getContentPane().setLayout(new AbsoluteLayout());
216
217 _mouseEventRouter = new MouseEventRouter(getJMenuBar(),
218 getContentPane());
219
220 // enable the glasspane-for capturing all mouse events
221 this.setGlassPane(_mouseEventRouter);
222
223 this.getGlassPane().setVisible(true);
224 this.getContentPane().setBackground(Color.white);
225 this.getContentPane().setFocusTraversalKeysEnabled(false);
226
227 addComponentListener(this);
228 pack();
229
230 // Reset windows to user specified size
231 // Must be done after initialising the content pane above!
232 setSizes(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
233
234 // UserSettings.ProfileName.set(FrameIO.ConvertToValidFramesetName(System.getProperty("user.name")));
235 String userName = UserSettings.ProfileName.get();
236 //UserSettings.UserName.set(UserSettings.ProfileName.get());
237
238 // Load documentation and start pages
239 FrameUtils.extractResources(false);
240 // Load fonts before loading any frames so the items on the frames will be able to access their fonts
241 Text.InitFonts();
242
243 Frame profile = loadProfile(userName);
244
245 // Need to display errors once things have been init otherwise
246 // exceptions occur if there are more than four messages neededing to be
247 // displayed.
248
249 Frame defaultProfile = loadProfile(UserSettings.DEFAULT_PROFILE_NAME);
250
251 Collection<String> warningMessages = new LinkedList<String>();
252 warningMessages.addAll(FrameUtils.ParseProfile(defaultProfile));
253 //Save the cursor if the defaultProfile had a custom cursor
254 Collection<Item> cursor = null;
255 if(FreeItems.hasCursor()){
256 cursor = new ArrayList<Item>();
257 cursor.addAll(FreeItems.getCursor());
258 }
259 warningMessages.addAll(FrameUtils.ParseProfile(profile));
260 if(cursor != null && !FreeItems.hasCursor()){
261 FreeItems.setCursor(cursor);
262 }
263
264 /*
265 * See Java bug ID 4016934. They say that window closed events are
266 * called once the JFrame is disposed.
267 */
268 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
269
270 // Expeditee handles its own repainting of AWT/Swing components
271 RepaintManager.setCurrentManager(ExpediteeRepaintManager.getInstance());
272
273 // Listen for save status to display during and after runtime
274 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
275
276 String full_version = System.getProperty("java.version");
277 String[] version_parts = full_version.split("\\.");
278 if (version_parts.length>=2) {
279 String version_str = version_parts[0] + "." + version_parts[1];
280 double version = Double.parseDouble(version_str);
281
282 if (version >= 1.6) {
283 // Set the drag and drop handler
284 _minimum_version6 = true;
285 setTransferHandler(FrameDNDTransferHandler.getInstance());
286 } else {
287 System.err.println("Upgrade to a (minimum) of Java 1.6 to enable drag and drop support in Expeditee");
288 }
289 }
290 else {
291 System.err.println("Unable to parse Java version number " + full_version + " to determin if Drag and Drop supported");
292
293 }
294
295
296 try {
297 warningMessages.addAll(Actions.Init());
298
299 Settings.Init();
300
301 DisplayIO.Init(this);
302 // Set visible must be just after DisplayIO.Init for the message box
303 // to
304 // be the right size
305 setVisible(true);
306
307 setupGraphics();
308
309 // required to accept TAB key
310 setFocusTraversalKeysEnabled(false);
311
312 // Must be loaded after setupGraphics if images are on the frame
313 // Turn off XRay mode and load the first frame
314 FrameGraphics.setMode(FrameGraphics.MODE_NORMAL, false);
315
316 // Go to the start frame if specified, otherwise go to the profile frame
317 Frame start = null;
318 if(_startFrame == null) {
319 _startFrame = UserSettings.StartFrame.get();
320 if(_startFrame != null && !Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
321 _startFrame = _startFrame + "1";
322 }
323 if((start = FrameIO.LoadFrame(_startFrame)) != null) {
324 // Make sure HomeFrame gets set
325 UserSettings.HomeFrame.set(start.getName());
326 //if (UserSettings.HomeFrame.get() == null) {
327 //UserSettings.HomeFrame.set(profile.getName());
328
329 //}
330 // Make sure the user can get back to the profile frame easily
331 //DisplayIO.addToBack(profile);
332 // Go to the start frame
333 DisplayIO.setCurrentFrame(start, true);
334 } else {
335 // If an invalid start frame was specified, show a warning
336 if(_startFrame != null) {
337 warningMessages.add("Unknown frame: " + _startFrame);
338 }
339 // Go to the profile frame
340 FrameUtils.loadFirstFrame(profile);
341 }
342 DisplayIO.UpdateTitle();
343
344 /*
345 * I think this can be moved back up to the top of the Go method
346 * now... It used to crash the program trying to print error
347 * messages up the top
348 */
349 for (String message : warningMessages)
350 MessageBay.warningMessage(message);
351
352 this.getContentPane().addKeyListener(FrameKeyboardActions.getInstance());
353 this.addKeyListener(FrameKeyboardActions.getInstance());
354
355 _mouseEventRouter.addExpediteeMouseListener(FrameMouseActions.getInstance());
356 _mouseEventRouter.addExpediteeMouseMotionListener(FrameMouseActions.getInstance());
357 _mouseEventRouter.addExpediteeMouseWheelListener(FrameMouseActions.getInstance());
358
359 // Dont refresh for the profile frame otherwise error messages are shown twice
360 if (!DisplayIO.getCurrentFrame().equals(profile)) {
361 FrameKeyboardActions.Refresh();
362 // If it's the profile frame just reparse it in order to display images/circles/widgets correctly
363 } else {
364 FrameUtils.Parse(profile);
365 }
366 // setVisible(true);
367 } catch (Exception e) {
368 e.printStackTrace();
369 Logger.Log(e);
370 }
371 }
372
373 /**
374 * @param userName
375 * @return
376 */
377 private Frame loadProfile(String userName) {
378 Frame profile = FrameIO.LoadProfile(userName);
379 if (profile == null) {
380 try {
381 profile = FrameIO.CreateNewProfile(userName);
382 } catch (Exception e) {
383 // TODO tell the user that there was a problem creating the
384 // profile frame and close nicely
385 e.printStackTrace();
386 assert (false);
387 }
388 }
389 return profile;
390 }
391
392 public Graphics2D g;
393
394 private void setupGraphics() {
395 if (g != null)
396 g.dispose();
397 g = (Graphics2D) this.getContentPane().getGraphics();
398 assert (g != null);
399 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
400 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
401 g.setFont(g.getFont().deriveFont(40f));
402 FrameGraphics.setDisplayGraphics(g);
403 }
404
405 // private int count = 0;
406 @Override
407 public void paint(Graphics g) {
408 // All this does is make sure the screen is repainted when the browser
409 // is moved so that some of the window is off the edge of the display
410 // then moved back into view
411 super.paint(g);
412 FrameGraphics.ForceRepaint();
413 // System.out.println("Paint " + count++);
414 }
415
416 /**
417 * @inheritDoc
418 */
419 public void componentResized(ComponentEvent e) {
420 setSizes(this.getSize());
421 setupGraphics();
422 FrameIO.RefreshCasheImages();
423 FrameGraphics.ForceRepaint();
424 }
425
426 /**
427 * @inheritDoc
428 */
429 public void componentMoved(ComponentEvent e) {
430 // FrameGraphics.setMaxSize(this.getSize());
431 }
432
433 /**
434 * @inheritDoc
435 */
436 public void componentShown(ComponentEvent e) {
437 DisplayIO.fullScreenTransitionPending=false;
438 }
439
440 /**
441 * @inheritDoc
442 */
443 public void componentHidden(ComponentEvent e) {
444 }
445
446 public void windowClosing(WindowEvent e) {
447 }
448
449 public void windowClosed(WindowEvent e) {
450 if (!DisplayIO.fullScreenTransitionPending) {
451 exit();
452 }
453 }
454
455 public void windowOpened(WindowEvent e) {
456 }
457
458 public void windowIconified(WindowEvent e) {
459 }
460
461 public void windowDeiconified(WindowEvent e) {
462 }
463
464 public void windowActivated(WindowEvent e) {
465 }
466
467 public void windowDeactivated(WindowEvent e) {
468 }
469
470 public void windowStateChanged(WindowEvent e) {
471 }
472
473 public int getDrawingAreaX() {
474 // return scrollPane.getLocationOnScreen().x;
475 return this.getLocationOnScreen().x;
476 }
477
478 public int getDrawingAreaY() {
479 // return scrollPane.getLocationOnScreen().y;
480 return this.getLocationOnScreen().y;
481 }
482
483 public void saveCompleted(SaveStateChangedEvent event) {
484 // if (isExisting()) {
485
486 // } else {
487 MessageBay.displayMessage("Save finished!", Color.BLUE);
488 // }
489 }
490
491 public void saveStarted(SaveStateChangedEvent event) {
492 // if (isExisting()) {
493
494 // } else {
495 String name = event.getEntity().getSaveName();
496 if (name == null)
497 name = "data";
498 MessageBay.displayMessage("Saving " + name + "...", Color.BLUE);
499 // }
500 }
501
502 /**
503 * Closes the browser and ends the application. Performs saving operations -
504 * halting until saves have completed. Feedback is given to the user while
505 * the application is exiting. Must call on the swing thread.
506 */
507 public void exit() {
508
509 // Set exiting flag
510 _isExiting = true;
511
512 MessageBay.displayMessage("System exiting...");
513
514 /**
515 * TODO: Prompt the user etc.
516 */
517
518 // TODO: Should we should a popup with a progress bar for user feedback?
519 // this would be nice and easy to do.
520 // Exit on a dedicated thread so that feedback can be obtained
521 new Exiter().start(); // this will exit the application
522 }
523
524 /**
525 * The system must exit on a different thread other than the swing thread so
526 * that the save threads can fire save-feedback to the swing thread and thus
527 * provide user feedback on asynchronous save operations.
528 *
529 * @author Brook Novak
530 *
531 */
532 private class Exiter extends Thread {
533
534 @Override
535 public void run() {
536
537 // The final save point for saveable entities
538 EntitySaveManager.getInstance().saveAll();
539 try {
540 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
541 } catch (InterruptedException e) {
542 e.printStackTrace();
543 }
544
545 // The final phase must save on the swing thread since dealing with
546 // the expeditee data model
547 SwingUtilities.invokeLater(new Runnable() {
548 public void run() {
549
550 // Stop any agents or simple programs
551 Simple.stop();
552 Actions.stopAgent();
553 // Wait for them to stop
554 try {
555 MessageBay
556 .displayMessage("Stopping Simple programs..."); // TODO:
557 /**
558 * Only stop if need to...
559 */
560 while (Simple.isProgramRunning()) {
561 Thread.sleep(100);
562 /* Brook: What purpose does this serve? */
563 }
564 MessageBay.displayMessage("Stopping Agents...");
565 /* TODO: Only stop if need to... */
566 while (Actions.isAgentRunning()) {
567 Thread.sleep(100); // Brook: What purpose does this
568 // serve?
569 }
570 } catch (Exception e) {
571
572 }
573
574 MessageBay.displayMessage("Saving current frame...");
575 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
576
577 MessageBay.displayMessage("Saving stats...");
578 StatsLogger.WriteStatsFile();
579
580 if (MailSession.getInstance() != null) {
581 if (MailSession.getInstance().finalise()) {
582 // TODO display this message before the finalising
583 // is done but only if the mail needs closing
584 MessageBay.displayMessage("Closed ExpMail...");
585 }
586 }
587
588 if (FrameShare.getInstance() != null) {
589 MessageBay.displayMessage("Stopping FrameServer...");
590 FrameShare.getInstance().finalise();
591 }
592
593 MessageBay.displayMessage("System exited");
594
595 // Finally remove the messages frameset
596 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH);
597
598 /*
599 * Create a new messages folder so that it doesn't throw
600 * Exceptions when two Expeditee's open at once and the
601 * second tries to save its messages
602 */
603 File file = new File(FrameIO.MESSAGES_PATH + "messages");
604 file.mkdirs();
605
606 Browser._hasExited = true;
607
608 System.exit(0);
609 }
610 });
611 }
612 }
613
614 /**
615 * Used to set up the the browser for use in testing.
616 *
617 * @return
618 */
619 public static Browser initializeForTesting() {
620 if (Browser._theBrowser == null) {
621 FrameShare.disableNetworking = true;
622 MailSession._autoConnect = false;
623
624 Browser.main(new String[]{});
625 try {
626 while (!isInitComplete()) {
627 Thread.sleep(10);
628 }
629 } catch (Exception e) {
630 }
631 }
632 return _theBrowser;
633 }
634
635}
Note: See TracBrowser for help on using the repository browser.