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

Last change on this file since 919 was 919, checked in by jts21, 10 years ago

Added license headers to all files, added full GPL3 license file, moved license header generator script to dev/bin/scripts

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(null);
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.