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

Last change on this file since 926 was 926, checked in by bln4, 10 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
RevLine 
[919]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
[4]19package org.expeditee.gui;
20
[10]21import java.awt.Color;
[4]22import java.awt.Dimension;
[760]23import java.awt.Font;
[4]24import java.awt.Graphics;
25import java.awt.Graphics2D;
[760]26import java.awt.GraphicsEnvironment;
[455]27import java.awt.Image;
[145]28import java.awt.MouseInfo;
29import java.awt.Point;
[4]30import java.awt.RenderingHints;
31import java.awt.Toolkit;
[738]32import java.awt.datatransfer.Clipboard;
33import java.awt.datatransfer.Transferable;
[4]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;
[348]39import java.io.File;
[535]40import java.net.Authenticator;
[455]41import java.net.URL;
[427]42import java.util.ArrayList;
[80]43import java.util.Collection;
[376]44import java.util.LinkedList;
[738]45import java.util.List;
[4]46
47import javax.swing.JFrame;
[121]48import javax.swing.RepaintManager;
[139]49import javax.swing.SwingUtilities;
[4]50
[10]51import org.expeditee.AbsoluteLayout;
[4]52import org.expeditee.actions.Actions;
[139]53import org.expeditee.actions.Simple;
[238]54import org.expeditee.agents.mail.MailSession;
[253]55import org.expeditee.importer.FrameDNDTransferHandler;
[738]56import org.expeditee.io.ExpClipReader;
[504]57import org.expeditee.io.ItemSelection;
[535]58import org.expeditee.io.ProxyAuth;
[738]59import org.expeditee.io.ItemSelection.ExpDataHandler;
[427]60import org.expeditee.items.Item;
[738]61import org.expeditee.items.ItemUtils;
[777]62import org.expeditee.items.Text;
[219]63import org.expeditee.items.widgets.WidgetCacheManager;
[298]64import org.expeditee.network.FrameShare;
[570]65import org.expeditee.settings.Settings;
66import org.expeditee.settings.UserSettings;
[419]67import org.expeditee.stats.Logger;
[139]68import org.expeditee.stats.StatsLogger;
69import org.expeditee.taskmanagement.EntitySaveManager;
70import org.expeditee.taskmanagement.SaveStateChangedEvent;
71import org.expeditee.taskmanagement.SaveStateChangedEventListener;
[4]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,
[139]83 WindowListener, WindowStateListener, SaveStateChangedEventListener {
[67]84
[4]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
[139]92 public static Browser _theBrowser = null;
[535]93
94 public static ProxyAuth proxyAuth = new ProxyAuth();
[348]95
[763]96 public static boolean _hasExited = false;
97
[320]98 private MouseEventRouter _mouseEventRouter;
[147]99
[139]100 // A flag which is set once the application is exiting.
101 private boolean _isExiting = false;
[67]102
[467]103 private boolean _minimum_version6 = false;
[311]104
[467]105 public boolean isMinimumVersion6() {
106 return _minimum_version6;
[309]107 }
108
[154]109 private static boolean _initComplete = false;
[667]110
111 private static String _startFrame = null;
[154]112
[4]113 /**
114 * Constructs a new Browser object, then launches it
115 *
116 * @param args
117 */
118 public static void main(String[] args) {
[667]119
120 if(args.length > 0) {
121 _startFrame = args[0];
122 if(! Character.isDigit(_startFrame.charAt(_startFrame.length() - 1)))
123 _startFrame = _startFrame + "1";
124 }
[147]125
[667]126
127
[139]128 // Prepare all expeditee and swing data on the AWT event thread.
129 SwingUtilities.invokeLater(new Runnable() {
130 public void run() {
[147]131 // MessageBay.supressMessages(true);
132
133 // MessageBay.supressMessages(false);
134
[139]135 _theBrowser = new Browser();
[455]136
[427]137 DisplayIO.refreshCursor();
[139]138 _theBrowser.requestFocus();
[147]139 FrameMouseActions.MouseX = MouseInfo.getPointerInfo()
140 .getLocation().x
141 - _theBrowser.getOrigin().x;
142 FrameMouseActions.MouseY = MouseInfo.getPointerInfo()
143 .getLocation().y
144 - _theBrowser.getOrigin().y;
[154]145 _initComplete = true;
[535]146
147 Authenticator.setDefault(proxyAuth);
[139]148 }
149 });
150
[4]151 }
[147]152
[145]153 public Point getOrigin() {
154 return getContentPane().getLocationOnScreen();
155 }
[348]156
[320]157 /**
[760]158 * @return The mouse event router used for this browser. Never null after
[348]159 * browser constructed.
[320]160 */
161 public MouseEventRouter getMouseEventRouter() {
162 return _mouseEventRouter;
163 }
[147]164
[139]165 /**
[147]166 * @return
[139]167 *
[147]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.
[139]171 *
172 * @see Browser#exit()
173 *
174 */
175 public boolean isExisting() {
176 return _isExiting;
177 }
[67]178
[763]179 public static boolean isInitComplete() {
[154]180 return _initComplete;
181 }
182
[4]183 public void setSizes(Dimension size) {
184 setSize(size);
185 setPreferredSize(size);
[80]186 Dimension paneSize = getContentPane().getSize();
187 FrameGraphics.setMaxSize(paneSize);
[4]188 }
189
[139]190 private Browser() {
[67]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;
[655]195 setLocation((int) (xpos - (UserSettings.InitialWidth.get() / 2)),
196 (int) (ypos - (UserSettings.InitialHeight.get() / 2)));
[4]197
[67]198 addWindowListener(this);
199 addWindowStateListener(this);
[233]200
[219]201 DisplayIO.addDisplayIOObserver(WidgetCacheManager.getInstance());
202 DisplayIO.addDisplayIOObserver(PopupManager.getInstance());
[67]203
[455]204
205 // set up the image used for the icon
206 try
207 {
[618]208 URL iconURL = ClassLoader.getSystemResource("org/expeditee/assets/icons/expediteeicon128.png");
[455]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
[699]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
[655]242 // UserSettings.ProfileName.set(FrameIO.ConvertToValidFramesetName(System.getProperty("user.name")));
243 String userName = UserSettings.ProfileName.get();
244 //UserSettings.UserName.set(UserSettings.ProfileName.get());
[781]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();
[147]250
[348]251 Frame profile = loadProfile(userName);
252
[233]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.
[4]256
[348]257 Frame defaultProfile = loadProfile(UserSettings.DEFAULT_PROFILE_NAME);
258
[376]259 Collection<String> warningMessages = new LinkedList<String>();
260 warningMessages.addAll(FrameUtils.ParseProfile(defaultProfile));
[427]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 }
[348]267 warningMessages.addAll(FrameUtils.ParseProfile(profile));
[427]268 if(cursor != null && !FreeItems.hasCursor()){
269 FreeItems.setCursor(cursor);
270 }
[348]271
272 /*
273 * See Java bug ID 4016934. They say that window closed events are
274 * called once the JFrame is disposed.
275 */
[147]276 setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
277
[121]278 // Expeditee handles its own repainting of AWT/Swing components
279 RepaintManager.setCurrentManager(ExpediteeRepaintManager.getInstance());
[147]280
[139]281 // Listen for save status to display during and after runtime
282 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
[147]283
[467]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 }
[154]297 }
[467]298 else {
299 System.err.println("Unable to parse Java version number " + full_version + " to determin if Drag and Drop supported");
300
301 }
302
[154]303
[4]304 try {
[233]305 warningMessages.addAll(Actions.Init());
[570]306
307 Settings.Init();
[4]308
[108]309 DisplayIO.Init(this);
[147]310 // Set visible must be just after DisplayIO.Init for the message box
311 // to
[108]312 // be the right size
313 setVisible(true);
[4]314
[108]315 setupGraphics();
[102]316
[108]317 // required to accept TAB key
318 setFocusTraversalKeysEnabled(false);
[147]319
320 // Must be loaded after setupGraphics if images are on the frame
321 // Turn off XRay mode and load the first frame
[282]322 FrameGraphics.setMode(FrameGraphics.MODE_NORMAL, false);
[667]323
324 // Go to the start frame if specified, otherwise go to the profile frame
325 Frame start = null;
[732]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 }
[667]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 }
[115]347 DisplayIO.UpdateTitle();
[147]348
[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 */
[108]354 for (String message : warningMessages)
[121]355 MessageBay.warningMessage(message);
[4]356
[667]357 this.getContentPane().addKeyListener(FrameKeyboardActions.getInstance());
[345]358 this.addKeyListener(FrameKeyboardActions.getInstance());
[147]359
[667]360 _mouseEventRouter.addExpediteeMouseListener(FrameMouseActions.getInstance());
361 _mouseEventRouter.addExpediteeMouseMotionListener(FrameMouseActions.getInstance());
362 _mouseEventRouter.addExpediteeMouseWheelListener(FrameMouseActions.getInstance());
[348]363
[513]364 // Dont refresh for the profile frame otherwise error messages are shown twice
365 if (!DisplayIO.getCurrentFrame().equals(profile)) {
[348]366 FrameKeyboardActions.Refresh();
[513]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 }
[147]371 // setVisible(true);
[108]372 } catch (Exception e) {
373 e.printStackTrace();
374 Logger.Log(e);
375 }
376 }
[4]377
[348]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
[121]397 public Graphics2D g;
[80]398
[7]399 private void setupGraphics() {
[71]400 if (g != null)
401 g.dispose();
[7]402 g = (Graphics2D) this.getContentPane().getGraphics();
[67]403 assert (g != null);
[7]404 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
405 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
406 g.setFont(g.getFont().deriveFont(40f));
407 FrameGraphics.setDisplayGraphics(g);
[4]408 }
409
[67]410 // private int count = 0;
[4]411 @Override
412 public void paint(Graphics g) {
[67]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
[4]416 super.paint(g);
[121]417 FrameGraphics.ForceRepaint();
[102]418 // System.out.println("Paint " + count++);
[4]419 }
420
421 /**
422 * @inheritDoc
423 */
424 public void componentResized(ComponentEvent e) {
[7]425 setSizes(this.getSize());
426 setupGraphics();
[67]427 FrameIO.RefreshCasheImages();
[121]428 FrameGraphics.ForceRepaint();
[4]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) {
[139]454 exit();
[4]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 }
[139]484
485 public void saveCompleted(SaveStateChangedEvent event) {
[147]486 // if (isExisting()) {
487
488 // } else {
489 MessageBay.displayMessage("Save finished!", Color.BLUE);
490 // }
[139]491 }
492
493 public void saveStarted(SaveStateChangedEvent event) {
[147]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 // }
[139]502 }
[147]503
[139]504 /**
[147]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.
[139]508 */
509 public void exit() {
[147]510
[139]511 // Set exiting flag
512 _isExiting = true;
[147]513
[139]514 MessageBay.displayMessage("System exiting...");
[147]515
[139]516 /**
517 * TODO: Prompt the user etc.
518 */
519
[147]520 // TODO: Should we should a popup with a progress bar for user feedback?
521 // this would be nice and easy to do.
[139]522 // Exit on a dedicated thread so that feedback can be obtained
523 new Exiter().start(); // this will exit the application
524 }
[147]525
[139]526 /**
[147]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
[139]529 * provide user feedback on asynchronous save operations.
530 *
531 * @author Brook Novak
[147]532 *
[139]533 */
534 private class Exiter extends Thread {
535
536 @Override
537 public void run() {
[147]538
[139]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
[238]547 // The final phase must save on the swing thread since dealing with
548 // the expeditee data model
[139]549 SwingUtilities.invokeLater(new Runnable() {
550 public void run() {
551
[147]552 // Stop any agents or simple programs
[139]553 Simple.stop();
554 Actions.stopAgent();
[147]555 // Wait for them to stop
556 try {
557 MessageBay
558 .displayMessage("Stopping Simple programs..."); // TODO:
[298]559 /**
560 * Only stop if need to...
561 */
[147]562 while (Simple.isProgramRunning()) {
[311]563 Thread.sleep(100);
564 /* Brook: What purpose does this serve? */
[139]565 }
[311]566 MessageBay.displayMessage("Stopping Agents...");
567 /* TODO: Only stop if need to... */
[147]568 while (Actions.isAgentRunning()) {
569 Thread.sleep(100); // Brook: What purpose does this
[154]570 // serve?
[139]571 }
[147]572 } catch (Exception e) {
573
[139]574 }
[147]575
576 MessageBay.displayMessage("Saving current frame...");
[139]577 FrameIO.SaveFrame(DisplayIO.getCurrentFrame());
578
[147]579 MessageBay.displayMessage("Saving stats...");
[139]580 StatsLogger.WriteStatsFile();
[242]581
[254]582 if (MailSession.getInstance() != null) {
[290]583 if (MailSession.getInstance().finalise()) {
[298]584 // TODO display this message before the finalising
585 // is done but only if the mail needs closing
586 MessageBay.displayMessage("Closed ExpMail...");
[290]587 }
[254]588 }
[147]589
[298]590 if (FrameShare.getInstance() != null) {
591 MessageBay.displayMessage("Stopping FrameServer...");
592 FrameShare.getInstance().finalise();
593 }
594
[147]595 MessageBay.displayMessage("System exited");
[311]596
[348]597 // Finally remove the messages frameset
598 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH);
[311]599
[348]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
[763]608 Browser._hasExited = true;
609
[139]610 System.exit(0);
611 }
612 });
613 }
614 }
615
[242]616 /**
617 * Used to set up the the browser for use in testing.
618 *
619 * @return
620 */
621 public static Browser initializeForTesting() {
[156]622 if (Browser._theBrowser == null) {
[311]623 FrameShare.disableNetworking = true;
[242]624 MailSession._autoConnect = false;
[311]625
[926]626 Browser.main(new String[]{});
[156]627 try {
628 while (!isInitComplete()) {
629 Thread.sleep(10);
630 }
631 } catch (Exception e) {
632 }
633 }
634 return _theBrowser;
635 }
636
[4]637}
Note: See TracBrowser for help on using the repository browser.