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

Last change on this file since 926 was 926, checked in by bln4, 9 years ago

The 'initializeForTesting' method was calling Browser->main() passing null which caused a null pointer exception. Now passes empty array.

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