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

Last change on this file since 1415 was 1415, checked in by bln4, 5 years ago

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

File size: 17.6 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.io.File;
22import java.io.IOException;
23import java.net.Authenticator;
24import java.security.KeyStoreException;
25import java.security.NoSuchAlgorithmException;
26import java.security.cert.CertificateException;
27import java.sql.SQLException;
28import java.util.ArrayList;
29import java.util.Collection;
30
31import org.expeditee.actions.Actions;
32import org.expeditee.actions.Simple;
33import org.expeditee.agents.mail.MailSession;
34import org.expeditee.auth.AuthenticatorBrowser;
35import org.expeditee.core.BlockingRunnable;
36import org.expeditee.core.Colour;
37import org.expeditee.core.Dimension;
38import org.expeditee.core.Point;
39import org.expeditee.gio.EcosystemManager;
40import org.expeditee.gio.EcosystemManager.Ecosystem;
41import org.expeditee.gio.GraphicsManager;
42import org.expeditee.gio.InputManager;
43import org.expeditee.gio.InputManager.WindowEventListener;
44import org.expeditee.gio.InputManager.WindowEventType;
45import org.expeditee.gio.gesture.StandardGestureActions;
46import org.expeditee.io.ProxyAuth;
47import org.expeditee.items.Item;
48import org.expeditee.items.ItemUtils;
49import org.expeditee.items.Text;
50import org.expeditee.items.widgets.WidgetCacheManager;
51import org.expeditee.network.FrameShare;
52import org.expeditee.settings.Settings;
53import org.expeditee.settings.UserSettings;
54import org.expeditee.stats.Logger;
55import org.expeditee.stats.StatsLogger;
56import org.expeditee.taskmanagement.EntitySaveManager;
57import org.expeditee.taskmanagement.SaveStateChangedEvent;
58import org.expeditee.taskmanagement.SaveStateChangedEventListener;
59
60/**
61 * The Main GUI class, comprises what people will see on the screen.<br>
62 * Note: Each Object (Item) is responsible for drawing itself on the screen.<br>
63 * Note2: The Frame is registered as a MouseListener and KeyListener, and
64 * processes any Events.
65 *
66 * TODO List:
67 *
68 * Back to standard:
69 * - JavaFX Text-hitting (requires JFX1.9)
70 * - Overlays and Vectors review
71 * - Pop-ups (unused in base Expeditee, only in Apollo)
72 * - Apollo input (test)
73 * - The rest of Apollo
74 * - Make sure clipping/invalidation takes twin-frames into account
75 * - Reinstate Simple input commands
76 * - Test LinkedTrack.paintInFreeSpace()
77 * - Test EmulatedTextItem.onMouseReleased(MouseEvent) (removed emulatedSource mouse button check)
78 * - Constrained placement of items
79 *
80 * Extra:
81 * - Self-describing input
82 * - Thread safety (most stuff on GIO event thread but Agents can start new threads)
83 * - Touch input
84 * - Better reflection??? (currently changes to the code break reflection as it relies on names)
85 * - JavaFX lag (always rendering to images doesn't gel well with JavaFX)
86 * - Swing widgets in JFX
87 * - JavaFX Widgets (exception on drawing?)
88 * - Reduce reliance on into-image rendering to improve JavaFX performance (utilise enforced-clip)
89 * - Block Swing ecosystem setup until window is available, or...
90 * - Reconfigure window-resized code so things are properly resized (PREFERRED).
91 * - Swing alpha-compositing of colours (currently alpha is ignored e.g. drop-shadow in transition).
92 * - Overly-thick extrusion lines (seems to depend on number of connected lines...)
93 * - Make FreeItems control pickup etc.
94 * - Remove MouseEventRouter
95 * - Highlighting (should be controlled by the items themselves)
96 * - Make Widgets into fully-fledged items (maybe???)
97 * - Merge Widget and HeavyDutyWidget
98 * - Redefine TextLayouts (relative/absolute layouts)
99 * - Settings exceptions (Password widget in JFX) (currently fixed with hack)
100 * - MessageBay (what did I mean by this specifically?)
101 * - Order-dependency of start-up code
102 * - MessageBay not refreshing at start-up
103 * - Invalidation hierarchy (item => frame => display controller area => window)
104 * - Add gesture data type checking
105 * - Paintable interface
106 * - EcosystemSpecific interface (utilise to enable/disable features based on ecosystem)
107 * - Remove/modify Mutable class (in Apollo.util, change to InOutReference)
108 * - Convert BlockingRunnable to interface
109 * - Modify Metronome to utilise Timeouts
110 * - Need AWT in FastAlphaEffect???
111 *
112 * General:
113 * - Tidy FrameGraphics
114 * - Comment
115 * - UserSettings.DEFAULT_PROFILE_NAME not actually a user setting
116 *
117 * Done:
118 * - Timers
119 * - Timer input animations
120 * - Finish DisplayController/FrameGraphics separation
121 * - Anchor constraints (create class)
122 * - Make MessageBay take lead from DisplayController
123 * - Make message bay display again (to do with above)
124 * - Tooltips (layout broken in Swing as window size not correct when tooltips laid out)
125 * - Frame transitions
126 * - Frame transitions in twin-frames mode drawing over each other
127 * - JFX DnD manager
128 * - Reinstate Simple
129 * - Reinstate exclude source
130 * - Reinstate commented code
131 * - Twin-frames division 0 at startup
132 * - Twin frames off-by-one frame size (draws a line of erroneous pixels)
133 * - MessageBay delays showing messages on start-up
134 * - JFX Antialiasing done on theScene??? Doesn't apply for our situation
135 * - Incorporate Clip class into code
136 * - Enforced clip
137 *
138 * @author cts16
139 * @author jdm18
140 */
141public class Browser implements SaveStateChangedEventListener {
142
143 public static final Ecosystem ECOSYSTEM_TYPE = Ecosystem.Swing;
144
145 public static Browser _theBrowser = null;
146
147 public static ProxyAuth proxyAuth = new ProxyAuth();
148
149 public static boolean _hasExited = false;
150
151 /** A flag which is set once the application is exiting. */
152 protected boolean _isExiting = false;
153
154 protected static boolean _initComplete = false;
155
156 private static String _startFrame = null;
157
158
159 /**
160 * Constructs a new Browser object, then launches it
161 *
162 * @param args
163 */
164 public static void main(String[] args) {
165 if (AuthenticatorBrowser.isAuthenticationRequired()) {
166 String starting_user_name = System.getProperty("user.name");
167 System.setProperty("startinguser.name", starting_user_name);
168 System.setProperty("user.name", AuthenticatorBrowser.USER_NOBODY);
169 }
170
171 // Parse the starting frame command-line argument
172 if(args.length > 0) {
173 setStartFrame(args[0]);
174 if(!Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
175 setStartFrame(getStartFrame() + "1");
176 }
177 } else {
178 setStartFrame("home1");
179 }
180
181 // Window icon must be set before initialisation
182 GraphicsManager.setWindowIcon(DisplayController.ICON_IMAGE);
183
184 // Setup the GIO ecosystem so it is ready when we need it
185 EcosystemManager.createEcosystem(ECOSYSTEM_TYPE);
186
187 try {
188 EcosystemManager.getMiscManager().runOnGIOThread(new BlockingRunnable() {
189 @Override
190 public void execute() {
191 init();
192 }
193 });
194 } catch (Throwable e) {
195 e.printStackTrace(System.err);
196 System.exit(1);
197 }
198
199 DisplayController.requestRefresh(false, () -> { MessageBay.showDelayedMessages(false); return true; });
200 }
201
202 public static void init() {
203 if (AuthenticatorBrowser.isAuthenticationRequired()) {
204 try {
205 _theBrowser = AuthenticatorBrowser.getInstance();
206 } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | IOException | ClassNotFoundException | SQLException e) {
207 e.printStackTrace();
208 }
209 } else {
210 _theBrowser = new Browser();
211 }
212
213 EcosystemManager.getGraphicsManager().requestFocus();
214
215 // Java's way of getting access to internet. Authenticating the user with their proxy username and password.
216 Authenticator.setDefault(proxyAuth);
217
218 _initComplete = true;
219 }
220
221 /**
222 * @return
223 *
224 * True if the application is about to exit. False if not. Note that this is
225 * only set once the window is in its closed state (not closing) or if the
226 * application has explicitly being requested to exit.
227 *
228 * @see Browser#exit()
229 *
230 */
231 public boolean isExiting() {
232 return _isExiting;
233 }
234
235 public static boolean isInitComplete() {
236 return _initComplete;
237 }
238
239 protected Browser(String mode) {
240 System.out.println("Running Expeditee in " + mode + " mode.");
241 }
242
243 protected Browser() {
244 // center the frame on the screen
245 GraphicsManager g = EcosystemManager.getGraphicsManager();
246 Dimension screen = g.getScreenSize();
247 double xpos = (screen.width - UserSettings.InitialWidth.get()) / 2.0;
248 double ypos = (screen.height - UserSettings.InitialHeight.get()) / 2.0;
249 g.setWindowLocation(new Point((int) xpos, (int) ypos));
250
251 DisplayController.Init();
252
253 DisplayController.addDisplayObserver(WidgetCacheManager.getInstance());
254 if (ECOSYSTEM_TYPE == Ecosystem.Swing) {
255 DisplayController.addDisplayObserver(PopupManager.getInstance());
256 }
257
258 setInputManagerWindowRoutines();
259
260 // Reset windows to user specified size
261 Dimension initialWindowSize = new Dimension(UserSettings.InitialWidth.get(), UserSettings.InitialHeight.get());
262 g.setWindowSize(initialWindowSize);
263
264 // Load documentation and start pages
265 FrameUtils.extractResources(false);
266
267 // Load fonts before loading any frames so the items on the frames will be able
268 // to access their fonts
269 Text.InitFonts();
270
271 Settings.Init();
272 Frame userProfile = loadProfiles();
273
274 if (!AuthenticatorBrowser.isAuthenticationRequired() && UserSettings.PublicAndPrivateResources) {
275 String userName = UserSettings.ProfileName.get();
276 if (!FrameIO.personalResourcesExist(userName)) {
277 FrameIO.setupPersonalResources(userName);
278 }
279 }
280 // Listen for save status to display during and after runtime
281 EntitySaveManager.getInstance().addSaveStateChangedEventListener(this);
282
283 try {
284 MessageBay.warningMessages(Actions.Init());
285
286 // Go to the start frame if specified, otherwise go to the profile frame
287 Frame start = null;
288 if (getStartFrame() == null) {
289 setStartFrame(UserSettings.StartFrame.get());
290 if (getStartFrame() != null && !Character.isDigit(getStartFrame().charAt(getStartFrame().length() - 1))) {
291 setStartFrame(getStartFrame() + "1");
292 }
293 }
294
295 if ((start = FrameIO.LoadFrame(getStartFrame())) != null) {
296 // Make sure HomeFrame gets set
297 UserSettings.HomeFrame.set(start.getName());
298
299 // Go to the start frame
300 DisplayController.setCurrentFrame(start, true);
301 } else {
302 // If an invalid start frame was specified, show a warning
303 if (getStartFrame() != null) {
304 MessageBay.warningMessage("Unknown frame: " + getStartFrame());
305 }
306
307 // Go to the profile frame
308 FrameUtils.loadFirstFrame(userProfile);
309 }
310
311 DisplayController.updateTitle();
312
313 // Don't refresh for the profile frame otherwise error messages are shown twice
314 if (!DisplayController.getCurrentFrame().equals(userProfile)) {
315 StandardGestureActions.Refresh();
316 // If it's the profile frame just reparse it in order to display
317 // images/circles/widgets correctly
318 } else {
319 FrameUtils.Parse(userProfile);
320 }
321 } catch (Exception e) {
322 e.printStackTrace();
323 Logger.Log(e);
324 }
325 }
326
327 @Override
328 public void saveCompleted(SaveStateChangedEvent event)
329 {
330 MessageBay.displayMessage("Save finished!", Colour.BLUE);
331 }
332
333 @Override
334 public void saveStarted(SaveStateChangedEvent event)
335 {
336 String name = event.getEntity().getSaveName();
337 if (name == null) {
338 name = "data";
339 }
340 MessageBay.displayMessage("Saving " + name + "...", Colour.BLUE);
341 }
342
343 /**
344 * Closes the browser and ends the application. Performs saving operations -
345 * halting until saves have completed. Feedback is given to the user while
346 * the application is exiting. Must call on the swing thread.
347 */
348 public void exit() {
349
350 // Set exiting flag
351 _isExiting = true;
352
353 MessageBay.displayMessage("System exiting...");
354
355 /**
356 * TODO: Prompt the user etc.
357 */
358
359 // TODO: Should we should a popup with a progress bar for user feedback?
360 // this would be nice and easy to do.
361 // Exit on a dedicated thread so that feedback can be obtained
362 new Exiter().start(); // this will exit the application
363 }
364
365 /**
366 * The system must exit on a different thread other than the swing thread so
367 * that the save threads can fire save-feedback to the swing thread and thus
368 * provide user feedback on asynchronous save operations.
369 *
370 * @author Brook Novak
371 *
372 */
373 private class Exiter extends Thread {
374
375 @Override
376 public void run() {
377
378 // The final save point for saveable entities
379 EntitySaveManager.getInstance().saveAll();
380 try {
381 EntitySaveManager.getInstance().waitUntilAllSavingFinished();
382 } catch (InterruptedException e) {
383 e.printStackTrace();
384 }
385
386 // Stop any agents or simple programs
387 Simple.stop();
388 Actions.stopAgent();
389 // Wait for them to stop
390 try {
391 // Only stop if need to...
392 // Brook: What purpose does this serve?
393 MessageBay.displayMessage("Stopping Simple programs...");
394 while (Simple.isProgramRunning()) {
395 Thread.sleep(100);
396 }
397
398 MessageBay.displayMessage("Stopping Agents...");
399 /* TODO: Only stop if need to... */
400 while (Actions.isAgentRunning()) {
401 Thread.sleep(100); // Brook: What purpose does this serve?
402 }
403 } catch (Exception e) {
404
405 }
406
407 MessageBay.displayMessage("Saving current frame...");
408 FrameIO.SaveFrame(DisplayController.getCurrentFrame());
409
410 MessageBay.displayMessage("Saving stats...");
411 StatsLogger.WriteStatsFile();
412
413 if (MailSession.getInstance() != null) {
414 if (MailSession.getInstance().finalise()) {
415 // TODO display this message before the finalising
416 // is done but only if the mail needs closing
417 MessageBay.displayMessage("Closed ExpMail...");
418 }
419 }
420
421 if (FrameShare.getInstance() != null) {
422 MessageBay.displayMessage("Stopping FrameServer...");
423 FrameShare.getInstance().finalise();
424 }
425
426 MessageBay.displayMessage("System exited");
427
428 // Finally remove the messages frameset
429 FrameIO.moveFrameset("messages", FrameIO.MESSAGES_PATH, false);
430
431 /*
432 * Create a new messages folder so that it doesn't throw
433 * Exceptions when two Expeditee's open at once and the
434 * second tries to save its messages
435 */
436 File file = new File(FrameIO.MESSAGES_PATH + "messages");
437 file.mkdirs();
438
439 Browser._hasExited = true;
440
441 System.exit(0);
442 }
443 }
444
445 /**
446 * Used to set up the the browser for use in testing.
447 *
448 * @return
449 */
450 public static Browser initializeForTesting()
451 {
452 if (Browser._theBrowser == null) {
453 FrameShare.disableNetworking = true;
454 MailSession._autoConnect = false;
455
456 Browser.main(new String[]{});
457 try {
458 while (!isInitComplete()) {
459 Thread.sleep(10);
460 }
461 } catch (Exception e) {
462 }
463 }
464 return _theBrowser;
465 }
466
467 private static void setInputManagerWindowRoutines() {
468 InputManager manager = EcosystemManager.getInputManager();
469
470 // Refresh the layout when the window resizes
471 manager.addWindowEventListener(new WindowEventListener() {
472 @Override
473 public void onWindowEvent(WindowEventType type)
474 {
475 if (type != WindowEventType.WINDOW_RESIZED) {
476 return;
477 }
478 DisplayController.refreshWindowSize();
479 FrameIO.RefreshCacheImages();
480 for (Frame frame : DisplayController.getFrames()) {
481 if (frame != null) {
482 ItemUtils.Justify(frame);
483 frame.refreshSize();
484 }
485 }
486 DisplayController.requestRefresh(false);
487 }
488 });
489
490 manager.addWindowEventListener(new WindowEventListener() {
491 @Override
492 public void onWindowEvent(WindowEventType type)
493 {
494 if (type != WindowEventType.MOUSE_EXITED_WINDOW) {
495 return;
496 }
497 StandardGestureActions.mouseExitedWindow();
498 }
499 });
500
501 manager.addWindowEventListener(new WindowEventListener() {
502 @Override
503 public void onWindowEvent(WindowEventType type)
504 {
505 if (type != WindowEventType.MOUSE_ENTERED_WINDOW) {
506 return;
507 }
508 StandardGestureActions.mouseEnteredWindow();
509 }
510 });
511
512 manager.addWindowEventListener(new WindowEventListener() {
513 @Override
514 public void onWindowEvent(WindowEventType type)
515 {
516 if (type != WindowEventType.WINDOW_CLOSED) {
517 return;
518 }
519 if (Browser._theBrowser != null) {
520 Browser._theBrowser.exit();
521 }
522 }
523 });
524 }
525
526 /**
527 * @return The user's profile frame.
528 */
529 public static Frame loadProfiles()
530 {
531 String defaultProfileName = UserSettings.DEFAULT_PROFILE_NAME;
532 String userName = UserSettings.ProfileName.get();
533
534 Frame userProfile = loadProfile(userName);
535 Frame defaultProfile = loadProfile(defaultProfileName);
536
537 MessageBay.warningMessages(FrameUtils.ParseProfile(defaultProfile));
538
539 // Save the cursor if the defaultProfile had a custom cursor
540 Collection<Item> cursor = null;
541 if(FreeItems.hasCursor()) {
542 cursor = new ArrayList<Item>();
543 cursor.addAll(FreeItems.getCursor());
544 }
545
546 MessageBay.warningMessages(FrameUtils.ParseProfile(userProfile));
547
548 if (cursor != null && !FreeItems.hasCursor()) {
549 FreeItems.setCursor(cursor);
550 }
551
552 return userProfile;
553 }
554
555 protected static Frame loadProfile(String userName) {
556 Frame profile = FrameIO.LoadProfile(userName);
557
558 if (profile == null) {
559 try {
560 profile = FrameIO.CreateNewProfile(userName, null, null);
561 } catch (Exception e) {
562 // TODO tell the user that there was a problem creating the
563 // profile frame and close nicely
564 e.printStackTrace();
565 assert (false);
566 }
567 }
568 return profile;
569 }
570
571 public static String getStartFrame() {
572 return _startFrame;
573 }
574
575 public static void setStartFrame(String _startFrame) {
576 Browser._startFrame = _startFrame;
577 }
578}
Note: See TracBrowser for help on using the repository browser.