source: trunk/src/org/expeditee/items/widgets/HeavyDutyInteractiveWidget.java@ 1046

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

Typo fix in comment

File size: 21.8 KB
Line 
1/**
2 * HeavyDutyInteractiveWidget.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.items.widgets;
20
21import java.awt.Color;
22import java.awt.Font;
23import java.awt.FontMetrics;
24import java.awt.GradientPaint;
25import java.awt.Graphics;
26import java.awt.Graphics2D;
27import java.awt.Rectangle;
28import java.awt.Shape;
29import java.awt.geom.Rectangle2D;
30import java.util.LinkedList;
31
32import javax.swing.JComponent;
33import javax.swing.SwingUtilities;
34
35import org.expeditee.gui.Browser;
36import org.expeditee.gui.DisplayIO;
37import org.expeditee.gui.FrameGraphics;
38import org.expeditee.gui.FreeItems;
39import org.expeditee.items.Item;
40import org.expeditee.items.ItemParentStateChangedEvent;
41import org.expeditee.items.Text;
42import org.expeditee.taskmanagement.EntityLoadManager;
43import org.expeditee.taskmanagement.EntitySaveManager;
44import org.expeditee.taskmanagement.LoadableEntity;
45import org.expeditee.taskmanagement.SaveableEntity;
46
47/**
48 * Interactive widgets that may require loading prior to usage, and/or saving.
49 *
50 * HeavyDutyInteractiveWidget provides a conventional asynchronous loading and saving scheme.
51 *
52 * When a loadable interactive widget is created. It is at a pending state - waiting to load
53 * until expeditee allocates a thread to begin loading. The swing component is set as not-visible.
54 *
55 * At the the loading stage, a loadable widget proccesses its load task and renders a
56 * load message/bar.
57 *
58 * Once loaded, the widget is like any other widget - and its visible state will be set as visible.
59 *
60 * When a HeavyDutyInteractiveWidget becomes visible / is anchored, it will register itself for saving at the next
61 * save point according to SaveEntityManager. Heavey duty widgets will only save at the save points
62 * if the widget still belongs to a frame (i.e. is not removed) and is anchored.
63 *
64 * Heavey duty widgets can also have an expiry on how long they should stay cached for. If they do have an
65 * expiry then it they unload once expired, and will re-load once in view again. Must take care for
66 * unloadable widgets because you must check your unloadable elements to whether they are unloaded or not
67 * every time your widget accessess them.
68 *
69 * When a heavy duty widget is deleted - any data consuming memory can be dumped temporarily if needed.
70 *
71 * @author Brook Novak
72 *
73 */
74public abstract class HeavyDutyInteractiveWidget extends InteractiveWidget implements LoadableEntity, SaveableEntity {
75
76 /** load finished */
77 public static final float LOAD_STATE_COMPLETED = 2.0f;
78
79 /** waiting to load. Could have been loaded previously but expired. */
80 public static final float LOAD_STATE_PENDING = 3.0f;
81
82 /** load failed */
83 public static final float LOAD_STATE_FAILED = 4.0f;
84
85 /** load was cancelled and still requires loading */
86 public static final float LOAD_STATE_INCOMPLETED = 5.0f;
87
88 // GUI Stuff
89 private static final String DEFAULT_LOAD_MESSAGE = "Loading";
90 private static final String PENDING_MESSAGE = "Pending";
91
92 private static final Color LOAD_BAR_PROGRESS_COLOR = new Color(103, 171, 203);
93 private static final Color LOAD_BAR_INDETERM_COLOR = Color.ORANGE;
94 private static final Color LOAD_BAR_HIGHLIGHT_COLOR = new Color(240, 240, 240);
95 private static final Color LOAD_SCREEN_COLOR = Color.LIGHT_GRAY;
96 private static final Color LOAD_SCREEN_COLOR_FREESPACE = FREESPACE_BACKCOLOR;
97
98 private static final Font LOAD_NORMAL_FONT = new Font("Arial", Font.BOLD, 12);
99 private static final Font LOAD_INDERTMIN_FONT = new Font("Arial", Font.ITALIC, 12);
100
101 private static final int BAR_HOROZONTIAL_MARGIN = 20;
102 private static final int BAR_HEIGHT = 40;
103
104 /** Unifies state transitions to a single thread. */
105 private HDWEventDispatcher eventDispatcher = null;
106 private LinkedList<HDWEvent> queuedEvents = new LinkedList<HDWEvent>();
107
108 // Model data
109 private float loadState; // handled by constructors and stateProccessor
110 private String screenMessage = DEFAULT_LOAD_MESSAGE;
111 private boolean hasCancelledBeenRequested;
112 private boolean isExpired = false;
113 private int cacheDepth = -1;
114
115 /**
116 * Constructor.
117 *
118 * @param source
119 *
120 * @param component
121 *
122 * @param minWidth
123 *
124 * @param maxWidth
125 *
126 * @param minHeight
127 *
128 * @param maxHeight
129 *
130 * @param cacheDepth
131 * Less or equal to zero for no cache management - i.e. use expeditees frame caching.
132 * Positive to limit cache; where the widget will be explicity unloaded
133 * ({@link HeavyDutyInteractiveWidget#unloadWidgetData()}) once the user has traversed
134 * cacheDepth frames without seeing this instance.
135 * The general rule is: the more memory your widget may take, the smaller the cache depth.
136 *
137 * @param skipLoad
138 * If true, the load state will be set to completed and the widget will not go through
139 * the loading proccess. Otherwise if true then the widget will go the loading phase.
140 */
141 protected HeavyDutyInteractiveWidget(Text source, JComponent component,
142 int minWidth, int maxWidth, int minHeight, int maxHeight, int cacheDepth, boolean skipLoad) {
143 super(source, component, minWidth, maxWidth, minHeight, maxHeight);
144
145 this.cacheDepth = cacheDepth;
146
147 if (skipLoad) {
148 loadState = LOAD_STATE_COMPLETED;
149 } else {
150 loadState = LOAD_STATE_PENDING;
151 component.setVisible(false); // not allowed to interact with it yet
152 component.setEnabled(false);
153 }
154 }
155
156 /**
157 * Chained constructor.
158 *
159 * @param source
160 * @param component
161 * @param minWidth
162 * @param maxWidth
163 * @param minHeight
164 * @param maxHeight
165 */
166 protected HeavyDutyInteractiveWidget(Text source, JComponent component,
167 int minWidth, int maxWidth, int minHeight, int maxHeight, int cacheDepth) {
168 this(source, component, minWidth, maxWidth, minHeight, maxHeight, cacheDepth, false);
169 }
170
171 /**
172 * Updates the percentage of the load. Should only call when in load phase.
173 *
174 * @param percent
175 * Must be between 0.0 and 1.0 inclusive for percentage. Or neagtive if indeterminant.
176 *
177 * @throws IllegalArgumentException
178 * If percent is larger than 1.0
179 *
180 * @throws IllegalStateException
181 * Load is not in progress
182 *
183 */
184 protected final void updateLoadPercentage(float percent) {
185 if (percent > 1.0f)
186 throw new IllegalArgumentException("loadState is larger than 1.0");
187
188 else if (!isInLoadProgress() ||
189 eventDispatcher == null ||
190 !eventDispatcher.isAlive())
191 throw new IllegalStateException("Load is not in progress");
192
193 // Assuming that this is called from eventDispatcher.
194 eventDispatcher.setLoadState(percent, false);
195 }
196
197
198 /**
199 * @return The current load state.
200 */
201 protected float getLoadState() {
202 return loadState;
203 }
204
205 /**
206 * @return True if in a loading phase.
207 */
208 protected boolean isInLoadProgress() {
209 return loadState <= 1.0f;
210 }
211
212 /**
213 * Sets the message which is displayed to the users while loading
214 * or when a load has failed.
215 *
216 * @param message
217 * A short human readable decription of the content being loaded
218 * or describing the failure.
219 * If null then the default load message will be assigned
220 */
221 protected final void setLoadScreenMessage(String message) {
222 this.screenMessage = message;
223 if (this.screenMessage == null) {
224 this.screenMessage = DEFAULT_LOAD_MESSAGE;
225 }
226
227 // Re-render loading state
228 FrameGraphics.invalidateArea(_swingComponent.getBounds());
229 FrameGraphics.requestRefresh(true);
230 }
231
232 @Override
233 public void paintInFreeSpace(Graphics g) {
234 if (loadState == LOAD_STATE_COMPLETED) super.paintInFreeSpace(g);
235 else paintLoadScreen(g, LOAD_SCREEN_COLOR_FREESPACE);
236 }
237
238 @Override
239 public void paint(Graphics g) {
240 if (loadState == LOAD_STATE_COMPLETED) {
241 super.paint(g);
242 } else {
243 paintLoadScreen(g, LOAD_SCREEN_COLOR);
244 this.paintLink((Graphics2D)g);
245 }
246
247 }
248
249 /**
250 * Rendersthe load bar / load messages
251 *
252 * @param g
253 */
254 private void paintLoadScreen(Graphics g, Color backgroundColor) {
255
256 if (Browser._theBrowser == null) return;
257
258 // Render shaded window over widget
259 g.setColor(backgroundColor);
260 g.fillRect(getX(), getY(), getWidth(), getHeight());
261
262 // Center the bar
263 int barX = getX() + BAR_HOROZONTIAL_MARGIN;
264 int barY = getY() + (getHeight() >> 1) - (BAR_HEIGHT >> 1);
265
266 int barWidth = getWidth() - (BAR_HOROZONTIAL_MARGIN * 2);
267 if (barWidth <= 0) barWidth = 10;
268
269 // Center the text
270 Font f = (loadState < 0.0f) ? LOAD_INDERTMIN_FONT : LOAD_NORMAL_FONT;
271 String message = (loadState == LOAD_STATE_PENDING || loadState == LOAD_STATE_INCOMPLETED) ?
272 PENDING_MESSAGE : screenMessage;
273
274 g.setFont(f);
275
276 // If need to re-calc the message drawing area... do so
277
278 FontMetrics fm = g.getFontMetrics(f);
279 Rectangle2D rect = fm.getStringBounds(message, g);
280 int textHeight = (int)(rect.getHeight());
281 int textWidth = (int)(rect.getWidth());
282
283 int textX = barX + ((barWidth - textWidth) >> 1);
284 if (textX <= 0) textX = BAR_HOROZONTIAL_MARGIN + 10;
285 int textY = barY + ((BAR_HEIGHT - textHeight) >> 1);
286 if (textY <= 0) textY = barY + 2;
287 textY += textHeight;
288
289 // Ensure that load bar and text doesn't spill over widgets invalidation area
290 Shape clipBackUp = g.getClip();
291 Rectangle tmpClip = (clipBackUp != null) ? clipBackUp.getBounds() :
292 new Rectangle(0, 0,
293 Browser._theBrowser.getContentPane().getWidth(),
294 Browser._theBrowser.getContentPane().getHeight());
295
296 g.setClip(tmpClip.intersection(getBounds()));
297
298
299 if (loadState < 0.0f) { // indeterminant
300
301 GradientPaint gp = new GradientPaint(
302 0, barY + (int)(BAR_HEIGHT * 0.8), LOAD_BAR_INDETERM_COLOR,
303 0, barY, LOAD_BAR_HIGHLIGHT_COLOR);
304 ((Graphics2D)g).setPaint(gp);
305
306 g.fillRect(barX, barY, barWidth, BAR_HEIGHT);
307
308 } else if (isInLoadProgress()) {
309
310 int progBarWidth = (int)(barWidth * loadState);
311
312 GradientPaint gp = new GradientPaint(
313 0, barY + (int)(BAR_HEIGHT * 0.8), LOAD_BAR_PROGRESS_COLOR,
314 0, barY, LOAD_BAR_HIGHLIGHT_COLOR);
315 ((Graphics2D)g).setPaint(gp);
316
317 g.fillRect(barX, barY, progBarWidth, BAR_HEIGHT);
318
319 }
320
321 g.setColor(Color.DARK_GRAY);
322 g.drawRect(barX, barY, barWidth, BAR_HEIGHT);
323
324 if (loadState == LOAD_STATE_FAILED)
325 g.setColor(Color.RED);
326
327 else g.setColor(Color.BLACK);
328
329
330
331 g.drawString(message, textX, textY);
332
333 g.setClip(clipBackUp);
334
335 }
336
337
338
339 /**
340 * Invoked by the load queue manager only.
341 */
342 public final void performLoad() {
343
344 try {
345 runEventAndWait(HDWEvent.Load);
346 } catch (InterruptedException e) {
347 loadState = LOAD_STATE_INCOMPLETED; // safety
348 e.printStackTrace();
349 }
350 }
351
352 /**
353 * Invoked by load manager
354 */
355 public final void cancelLoadRequested() {
356 hasCancelledBeenRequested = true;
357 cancelLoadWidgetData();
358 }
359
360 /**
361 * @return
362 * True if cancel has been requested. This is reset before a new load phase.
363 */
364 protected boolean hasCancelBeenRequested() {
365 return hasCancelledBeenRequested;
366 }
367
368 /**
369 *
370 * @return
371 * True if this widget is in an expired state
372 */
373 protected boolean isExpired() {
374 return isExpired;
375 }
376
377 protected boolean isLoaded() {
378 return this.loadState == LOAD_STATE_COMPLETED;
379 }
380
381 /**
382 * Important: Must be called otherwise the widget states may become unstable.
383 */
384 @Override
385 public void onDelete() {
386 super.onDelete();
387 // Evenetually - unload the data - with the intention of possible recovery, but not saving...
388 queueEvent(HDWEvent.UnloadTMP);
389 }
390
391 /**
392 *
393 * Invoked when it is time to perform all asynchronous (heavey duty) loading for the widget.
394 *
395 * The convention is to load until cancelLoadWidgetData is invoked. Once this is invoked it is
396 * the widgets load logic's choice to whether to acknowledge it. Note that proceeding
397 * loadable entities must wait until this returns - so it is best to heed the as soon as possible.
398 *
399 * Use updateLoadPercentage to update the load bar and setLoadScreenMessage to set the message
400 * for load feedback to users. It will start with the default loading message and progress at 0%.
401 *
402 * @return
403 * The final load state. Must be either:
404 * <ul>
405 * <li>LOAD_STATE_COMPLETED
406 * <li>LOAD_STATE_FAILED
407 * <li>LOAD_STATE_INCOMPLETED
408 * </ul>
409 * If not, then LOAD_STATE_FAILED is assumed - and an exception trace will be printed.
410 */
411 protected abstract float loadWidgetData();
412
413 /**
414 * @see loadWidgetData, hasCancelBeenRequested
415 */
416 protected void cancelLoadWidgetData() {}
417
418 @Override
419 protected void onParentStateChanged(int eventType) {
420
421 switch (eventType) {
422
423 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
424 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
425 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
426 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
427
428 // When anchored to the window, then requeue for loading iff load state
429 // is currently pending or incomplete
430 if (hasCancelledBeenRequested ||
431 loadState == LOAD_STATE_INCOMPLETED ||
432 loadState == LOAD_STATE_PENDING) { // if needing to load - then load
433 EntityLoadManager.getInstance().queue(this, getLoadDelayTime());
434 }
435
436 // Ensure that registered for saving at next save point
437 EntitySaveManager.getInstance().register(this);
438
439 // Ensure is cached
440 WidgetCacheManager.cacheWidget(this);
441
442 break;
443
444
445 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
446 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY:
447 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
448
449 // Whenever the widget is not longer in view then cancel current loading proccess
450 // if currently loading. This must be performed later because this event occurs
451 // before the widget has had a chance to bee moved into free space.
452 SwingUtilities.invokeLater(new DoCancelLoad()); // proccess on this thread later.
453
454 // Always unregister from save point.
455 EntitySaveManager.getInstance().unregister(this);
456
457 // If the widget has been removed then unregister from caching
458 // So that if deleted then won't hang around in cache
459 if (eventType == ItemParentStateChangedEvent.EVENT_TYPE_REMOVED) {
460 WidgetCacheManager.uncacheWidget(this);
461 // If removed via overlay, then cached frames may still contain overlay with the widget...
462 }
463 break;
464 }
465 }
466
467 private class DoCancelLoad implements Runnable {
468 public void run() {
469
470 for (Item i : getItems()) { // Only cancel if the widget is not floating. Allow loading while in free space
471 if (FreeItems.getInstance().contains(i))
472 return;
473 }
474
475 // TODO: One problem with loading in free space is that there is no way of cancelling
476 // if the heavy duty widget is deleted in free space while loading.
477 // This is just an inefficiency.
478 EntityLoadManager.getInstance().cancel(HeavyDutyInteractiveWidget.this);
479
480 }
481 }
482
483 /**
484 * @return The time to delay before loading begin when first in view.
485 */
486 public abstract int getLoadDelayTime();
487
488 /**
489 * Invoked by save manager
490 */
491 public final void performSave() {
492 try {
493 runEventAndWait(HDWEvent.Save);
494 } catch (InterruptedException e) {
495 e.printStackTrace();
496 }
497 }
498
499
500 /**
501 * Called by dedicated thread to save data at a save point.
502 * See EntitySaveManager for more inforamtion about save points.
503 *
504 * Heavey duty widgets will only save at the save points if the widget
505 * still belongs to a frame and is anchored.
506 */
507 protected abstract void saveWidgetData();
508
509 /**
510 * Only invoked if has cache expiry and has expired. Only if this is no longer visible.
511 */
512 void expire() {
513 runEventLater(HDWEvent.Unload);
514 }
515
516 /**
517 * Invoked if this widget has a cache expiry and has been expired.
518 * The protocol is that you should unload anything in memory...
519 * This should be quick, e.g. setting references to null / disposing.
520 * Pupose is to free memory.
521 *
522 * Important note:
523 * It is possible for a widget to expire before it had a chance to be saved,
524 * or even when saving. Thus must be careful.
525 * A simple approach would be to check the saving flag
526 *
527 *
528 */
529 protected abstract void unloadWidgetData();
530
531 /**
532 * This is invoked asynronously when the item is deleted. This widget
533 * will be added to an undo list in memory.
534 *
535 * The intention is that the implementation should release its memory as if it were
536 * expired - But it will not be saved. Thus the data should be dumped to a temporary file
537 * so it can be recovered if it is recalled (un-deleted). Note that this is not the same as saving -
538 * the user would not expect the widget to save state if deleted.
539 *
540 * This is never invoke <i>while</i> loading / saving / expiring. The operations
541 * are mutually exclusive. It can be invoked when the widget is not yet loaded.
542 *
543 */
544 protected abstract void tempUnloadWidgetData();
545
546 /**
547 * The cache depth is measured by how many frames the user can traverse through until
548 * the widget should expire.
549 *
550 * This is immutable.
551 *
552 * @return
553 * The cache depth for this interactive widget. less or equal to zero for no cache expiry.
554 */
555 public final int getCacheDepth() {
556 return cacheDepth;
557 }
558
559 private synchronized void runEventAndWait(HDWEvent event) throws InterruptedException {
560 queueEvent(event);
561 eventDispatcher.join();
562 }
563
564 private void runEventLater(HDWEvent event) {
565 queueEvent(event);
566 }
567
568 private void queueEvent(HDWEvent event) {
569
570 synchronized(queuedEvents) {
571
572 if (!queuedEvents.contains(event))
573 queuedEvents.add(event);
574
575
576 if (eventDispatcher == null || !eventDispatcher.isAlive()) {
577 eventDispatcher = new HDWEventDispatcher();
578 eventDispatcher.start();
579 }
580
581 }
582
583 }
584
585
586 /**
587 * Unified state management. Load states are handled by one thread always.
588 * Only has one instance - per widget instance, of this at any given time,
589 *
590 * @author Brook
591 *
592 */
593 private class HDWEventDispatcher extends Thread {
594
595 /**
596 * @param loadState
597 * Can be any of the following values.
598 * <ul>
599 * <li>PENDING_STATE if pending.
600 * <li>COMPLETED_STATE if loaded.
601 * <li>between 0.0f and 1.0f inclusive if loading: represents progress percent complete.
602 * <li>FAILED_STATE if failed.
603 * <li>INCOMPLETED_STATE if load was unable to complete.
604 * <li>negative if loading but indeterminant.
605 * </ul>
606 *
607 */
608 private void setLoadState(float state, boolean expired) {
609 assert (state == LOAD_STATE_FAILED || state != LOAD_STATE_INCOMPLETED ||
610 state != LOAD_STATE_PENDING || state != LOAD_STATE_COMPLETED ||
611 state < 1.0f) ;
612
613 assert (!expired ||
614 (expired && state == LOAD_STATE_PENDING));
615
616
617 isExpired = expired;
618 loadState = state;
619
620 if (loadState == LOAD_STATE_COMPLETED) { // set enabled state - show the swing components
621 SwingUtilities.invokeLater(new Runnable() {
622 public void run() {
623 _swingComponent.setVisible(true);
624 _swingComponent.setEnabled(true);
625 }
626 });
627
628 } else if(expired) { // disable/hide swing GUI when expires, like a reset
629
630 SwingUtilities.invokeLater(new Runnable() {
631 public void run() {
632 _swingComponent.setVisible(false);
633 _swingComponent.setEnabled(false);
634 }
635 });
636 }
637
638 // Re-render loading state - if not expired
639 if (!expired) {
640 FrameGraphics.invalidateArea(new Rectangle(getX(), getY(), getWidth(), getHeight()));
641 FrameGraphics.requestRefresh(true);
642 }
643 }
644
645 /**
646 * All state transitions performed by one thread.
647 */
648 public void run() {
649
650 while (true) { // keep processing tasks
651
652 HDWEvent event = null;
653
654 synchronized(queuedEvents) {
655 if (queuedEvents.isEmpty()) return;
656 event = queuedEvents.remove();
657 }
658
659 if (event == HDWEvent.Load) {
660 doLoad();
661 } else if (event == HDWEvent.Save) {
662 doSave(); // does not change state
663 } else if (event == HDWEvent.Unload){
664 doUnload();
665 } else {
666 assert(event == HDWEvent.UnloadTMP);
667 doTempUnload();
668 }
669
670
671 }
672 }
673
674 private void doLoad() {
675
676 // Check that not already loaded.
677 // if (loadState == LOAD_STATE_COMPLETED ||
678 // loadState == LOAD_STATE_FAILED) return;
679 if (loadState == LOAD_STATE_COMPLETED) return;
680
681 // Only load if in view
682 if (!isFloating() && getParentFrame() != DisplayIO.getCurrentFrame())
683 return;
684
685 // Reset flag.
686 hasCancelledBeenRequested = false;
687
688 // Set the load state as loading... 0%
689 setLoadState(0.0f, false);
690
691 float finalState = LOAD_STATE_FAILED;
692
693 try {
694 finalState = loadWidgetData();
695 } catch (Exception e) {
696 e.printStackTrace();
697 }
698
699 // Safety check for return state
700 try {
701 if (finalState != LOAD_STATE_COMPLETED
702 && finalState != LOAD_STATE_FAILED
703 && finalState != LOAD_STATE_INCOMPLETED) {
704 throw new Exception("ERROR: Bad return state: " + finalState);
705 }
706 } catch (Exception e) {
707 e.printStackTrace();
708 finalState = LOAD_STATE_FAILED;
709 }
710
711 // Set the final state
712 setLoadState(finalState, false);
713
714 }
715
716 private void doSave() {
717
718 // Only save if still belongs to a frame
719 if (!isFloating() && getParentFrame() != null) {
720 saveWidgetData();
721 }
722
723 }
724
725 private void doUnload() {
726
727 // Reset the load state
728 setLoadState(LOAD_STATE_PENDING, true);
729
730 // Get rid of memory
731 unloadWidgetData();
732
733 }
734
735 private void doTempUnload() {
736 // Reset the load state
737 setLoadState(LOAD_STATE_PENDING, true);
738
739 // Get rid of memory
740 tempUnloadWidgetData();
741 }
742
743
744
745 }
746
747 private enum HDWEvent {
748 Save,
749 Load,
750 Unload,
751 UnloadTMP
752 }
753
754
755}
Note: See TracBrowser for help on using the repository browser.