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

Last change on this file since 963 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: 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 proccessing 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.