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

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

Changes from private to protected to allow Browser to be inherited by another class, such as an experimental version of Expeditee using JFX events

File size: 18.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.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 protected MouseEventRouter _mouseEventRouter;
90
91 // A flag which is set once the application is exiting.
92 protected boolean _isExiting = false;
93
94 protected boolean _minimum_version6 = false;
95
96 public boolean isMinimumVersion6() {
97 return _minimum_version6;
98 }
99
100 protected static boolean _initComplete = false;
101
102 protected 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 protected 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 used to be done here, but now has been moved to later in the method
216 // as is works better when put in a JFXPanel
217 //this.getContentPane().setLayout(new AbsoluteLayout());
218
219 _mouseEventRouter = new MouseEventRouter(getJMenuBar(),
220 getContentPane());
221
222 // enable the glasspane-for capturing all mouse events
223 this.setGlassPane(_mouseEventRouter);
224
225 this.getGlassPane().setVisible(true);
226 this.getContentPane().setBackground(Color.white);
227 this.getContentPane().setFocusTraversalKeysEnabled(false);
228
229 addComponentListener(this);
230 pack();
231 this.getContentPane().setLayout(new AbsoluteLayout()); // moved to here to work with JFXPanel
232
233 // Reset windows to user specified size
234 // Must be done after initialising the content pane above!
235 setSizes(new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get()));
236
237 // UserSettings.ProfileName.set(FrameIO.ConvertToValidFramesetName(System.getProperty("user.name")));
238 String userName = UserSettings.ProfileName.get();
239 //UserSettings.UserName.set(UserSettings.ProfileName.get());
240
241 // Load documentation and start pages
242 FrameUtils.extractResources(false);
243 // Load fonts before loading any frames so the items on the frames will be able to access their fonts
244 Text.InitFonts();
245
246 Frame profile = loadProfile(userName);
247
248 // Need to display errors once things have been init otherwise
249 // exceptions occur if there are more than four messages neededing to be
250 // displayed.
251
252 Frame defaultProfile = loadProfile(UserSettings.DEFAULT_PROFILE_NAME);
253
254 Collection<String> warningMessages = new LinkedList<String>();
255 warningMessages.addAll(FrameUtils.ParseProfile(defaultProfile));
256 //Save the cursor if the defaultProfile had a custom cursor
257 Collection<Item> cursor = null;
258 if(FreeItems.hasCursor()){
259 cursor = new ArrayList<Item>();
260 cursor.addAll(FreeItems.getCursor());
261 }
262 warningMessages.addAll(FrameUtils.ParseProfile(profile));
263 if(cursor != null && !FreeItems.hasCursor()){
264 FreeItems.setCursor(cursor);
265 }
266
267 /*
268 * See Java bug ID 4016934. They say that window closed events are
269 * called once the JFrame is disposed.
270 */
271 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
272
273 // Expeditee handles its own repainting of AWT/Swing components
274 RepaintManager.setCurrentManager(ExpediteeRepaintManager.getInstance());
275
276 // Listen for save status to display during and after runtime
277 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
278
279 String full_version = System.getProperty("java.version");
280 String[] version_parts = full_version.split("\\.");
281 if (version_parts.length>=2) {
282 String version_str = version_parts[0] + "." + version_parts[1];
283 double version = Double.parseDouble(version_str);
284
285 if (version >= 1.6) {
286 // Set the drag and drop handler
287 _minimum_version6 = true;
288 setTransferHandler(FrameDNDTransferHandler.getInstance());
289 } else {
290 System.err.println("Upgrade to a (minimum) of Java 1.6 to enable drag and drop support in Expeditee");
291 }
292 }
293 else {
294 System.err.println("Unable to parse Java version number " + full_version + " to determin if Drag and Drop supported");
295
296 }
297
298
299 try {
300 warningMessages.addAll(Actions.Init());
301
302 Settings.Init();
303
304 DisplayIO.Init(this);
305 // Set visible must be just after DisplayIO.Init for the message box
306 // to
307 // be the right size
308 setVisible(true);
309
310 setupGraphics();
311
312 // required to accept TAB key
313 setFocusTraversalKeysEnabled(false);
314
315 // Must be loaded after setupGraphics if images are on the frame
316 // Turn off XRay mode and load the first frame
317 FrameGraphics.setMode(FrameGraphics.MODE_NORMAL, false);
318
319 // Go to the start frame if specified, otherwise go to the profile frame
320 Frame start = null;
321 if(_startFrame == null) {
322 _startFrame = UserSettings.StartFrame.get();
323 if(_startFrame != null && !Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
324 _startFrame = _startFrame + "1";
325 }
326 if((start = FrameIO.LoadFrame(_startFrame)) != null) {
327 // Make sure HomeFrame gets set
328 UserSettings.HomeFrame.set(start.getName());
329 //if (UserSettings.HomeFrame.get() == null) {
330 //UserSettings.HomeFrame.set(profile.getName());
331
332 //}
333 // Make sure the user can get back to the profile frame easily
334 //DisplayIO.addToBack(profile);
335 // Go to the start frame
336 DisplayIO.setCurrentFrame(start, true);
337 } else {
338 // If an invalid start frame was specified, show a warning
339 if(_startFrame != null) {
340 warningMessages.add("Unknown frame: " + _startFrame);
341 }
342 // Go to the profile frame
343 FrameUtils.loadFirstFrame(profile);
344 }
345 DisplayIO.UpdateTitle();
346
347 /*
348 * I think this can be moved back up to the top of the Go method
349 * now... It used to crash the program trying to print error
350 * messages up the top
351 */
352 for (String message : warningMessages)
353 MessageBay.warningMessage(message);
354
355 this.getContentPane().addKeyListener(FrameKeyboardActions.getInstance());
356 this.addKeyListener(FrameKeyboardActions.getInstance());
357
358 _mouseEventRouter.addExpediteeMouseListener(FrameMouseActions.getInstance());
359 _mouseEventRouter.addExpediteeMouseMotionListener(FrameMouseActions.getInstance());
360 _mouseEventRouter.addExpediteeMouseWheelListener(FrameMouseActions.getInstance());
361
362 // Dont refresh for the profile frame otherwise error messages are shown twice
363 if (!DisplayIO.getCurrentFrame().equals(profile)) {
364 FrameKeyboardActions.Refresh();
365 // If it's the profile frame just reparse it in order to display images/circles/widgets correctly
366 } else {
367 FrameUtils.Parse(profile);
368 }
369 // setVisible(true);
370 } catch (Exception e) {
371 e.printStackTrace();
372 Logger.Log(e);
373 }
374 }
375
376 /**
377 * @param userName
378 * @return
379 */
380 protected Frame loadProfile(String userName) {
381 Frame profile = FrameIO.LoadProfile(userName);
382 if (profile == null) {
383 try {
384 profile = FrameIO.CreateNewProfile(userName);
385 } catch (Exception e) {
386 // TODO tell the user that there was a problem creating the
387 // profile frame and close nicely
388 e.printStackTrace();
389 assert (false);
390 }
391 }
392 return profile;
393 }
394
395 public Graphics2D g;
396
397 protected void setupGraphics() {
398 if (g != null)
399 g.dispose();
400 g = (Graphics2D) this.getContentPane().getGraphics();
401 assert (g != null);
402 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
403 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
404 g.setFont(g.getFont().deriveFont(40f));
405 FrameGraphics.setDisplayGraphics(g);
406 }
407
408 // private int count = 0;
409 @Override
410 public void paint(Graphics g) {
411 // All this does is make sure the screen is repainted when the browser
412 // is moved so that some of the window is off the edge of the display
413 // then moved back into view
414 super.paint(g);
415 FrameGraphics.ForceRepaint();
416 // System.out.println("Paint " + count++);
417 }
418
419 /**
420 * @inheritDoc
421 */
422 public void componentResized(ComponentEvent e) {
423 setSizes(this.getSize());
424 setupGraphics();
425 FrameIO.RefreshCasheImages();
426 FrameGraphics.ForceRepaint();
427 }
428
429 /**
430 * @inheritDoc
431 */
432 public void componentMoved(ComponentEvent e) {
433 // FrameGraphics.setMaxSize(this.getSize());
434 }
435
436 /**
437 * @inheritDoc
438 */
439 public void componentShown(ComponentEvent e) {
440 DisplayIO.fullScreenTransitionPending=false;
441 }
442
443 /**
444 * @inheritDoc
445 */
446 public void componentHidden(ComponentEvent e) {
447 }
448
449 public void windowClosing(WindowEvent e) {
450 }
451
452 public void windowClosed(WindowEvent e) {
453 if (!DisplayIO.fullScreenTransitionPending) {
454 exit();
455 }
456 }
457
458 public void windowOpened(WindowEvent e) {
459 }
460
461 public void windowIconified(WindowEvent e) {
462 }
463
464 public void windowDeiconified(WindowEvent e) {
465 }
466
467 public void windowActivated(WindowEvent e) {
468 }
469
470 public void windowDeactivated(WindowEvent e) {
471 }
472
473 public void windowStateChanged(WindowEvent e) {
474 }
475
476 public int getDrawingAreaX() {
477 // return scrollPane.getLocationOnScreen().x;
478 return this.getLocationOnScreen().x;
479 }
480
481 public int getDrawingAreaY() {
482 // return scrollPane.getLocationOnScreen().y;
483 return this.getLocationOnScreen().y;
484 }
485
486 public void saveCompleted(SaveStateChangedEvent event) {
487 // if (isExisting()) {
488
489 // } else {
490 MessageBay.displayMessage("Save finished!", Color.BLUE);
491 // }
492 }
493
494 public void saveStarted(SaveStateChangedEvent event) {
495 // if (isExisting()) {
496
497 // } else {
498 String name = event.getEntity().getSaveName();
499 if (name == null)
500 name = "data";
501 MessageBay.displayMessage("Saving " + name + "...", Color.BLUE);
502 // }
503 }
504
505 /**
506 * Closes the browser and ends the application. Performs saving operations -
507 * halting until saves have completed. Feedback is given to the user while
508 * the application is exiting. Must call on the swing thread.
509 */
510 public void exit() {
511
512 // Set exiting flag
513 _isExiting = true;
514
515 MessageBay.displayMessage("System exiting...");
516
517 /**
518 * TODO: Prompt the user etc.
519 */
520
521 // TODO: Should we should a popup with a progress bar for user feedback?
522 // this would be nice and easy to do.
523 // Exit on a dedicated thread so that feedback can be obtained
524 new Exiter().start(); // this will exit the application
525 }
526
527 /**
528 * The system must exit on a different thread other than the swing thread so
529 * that the save threads can fire save-feedback to the swing thread and thus
530 * provide user feedback on asynchronous save operations.
531 *
532 * @author Brook Novak
533 *
534 */
535 private class Exiter extends Thread {
536
537 @Override
538 public void run() {
539
540 // The final save point for saveable entities
541 EntitySaveManager.getInstance().saveAll();
542 try {
543 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
544 } catch (InterruptedException e) {
545 e.printStackTrace();
546 }
547
548 // The final phase must save on the swing thread since dealing with
549 // the expeditee data model
550 SwingUtilities.invokeLater(new Runnable() {
551 public void run() {
552
553 // Stop any agents or simple programs
554 Simple.stop();
555 Actions.stopAgent();
556 // Wait for them to stop
557 try {
558 MessageBay
559 .displayMessage("Stopping Simple programs..."); // TODO:
560 /**
561 * Only stop if need to...
562 */
563 while (Simple.isProgramRunning()) {
564 Thread.sleep(100);
565 /* Brook: What purpose does this serve? */
566 }
567 MessageBay.displayMessage("Stopping Agents...");
568 /* TODO: Only stop if need to... */
569 while (Actions.isAgentRunning()) {
570 Thread.sleep(100); // Brook: What purpose does this
571 // serve?
572 }
573 } catch (Exception e) {
574
575 }
576
577 MessageBay.displayMessage("Saving current frame...");
578 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
579
580 MessageBay.displayMessage("Saving stats...");
581 StatsLogger.WriteStatsFile();
582
583 if (MailSession.getInstance() != null) {
584 if (MailSession.getInstance().finalise()) {
585 // TODO display this message before the finalising
586 // is done but only if the mail needs closing
587 MessageBay.displayMessage("Closed ExpMail...");
588 }
589 }
590
591 if (FrameShare.getInstance() != null) {
592 MessageBay.displayMessage("Stopping FrameServer...");
593 FrameShare.getInstance().finalise();
594 }
595
596 MessageBay.displayMessage("System exited");
597
598 // Finally remove the messages frameset
599 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH);
600
601 /*
602 * Create a new messages folder so that it doesn't throw
603 * Exceptions when two Expeditee's open at once and the
604 * second tries to save its messages
605 */
606 File file = new File(FrameIO.MESSAGES_PATH + "messages");
607 file.mkdirs();
608
609 Browser._hasExited = true;
610
611 System.exit(0);
612 }
613 });
614 }
615 }
616
617 /**
618 * Used to set up the the browser for use in testing.
619 *
620 * @return
621 */
622 public static Browser initializeForTesting() {
623 if (Browser._theBrowser == null) {
624 FrameShare.disableNetworking = true;
625 MailSession._autoConnect = false;
626
627 Browser.main(new String[]{});
628 try {
629 while (!isInitComplete()) {
630 Thread.sleep(10);
631 }
632 } catch (Exception e) {
633 }
634 }
635 return _theBrowser;
636 }
637
638}
Note: See TracBrowser for help on using the repository browser.