001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.progress.module;
043:
044: import java.awt.Component;
045: import java.awt.event.ActionListener;
046: import java.util.ArrayList;
047: import java.util.Collection;
048: import java.util.Collections;
049: import java.util.HashMap;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.logging.Level;
053: import java.util.logging.Logger;
054: import javax.swing.SwingUtilities;
055: import javax.swing.Timer;
056: import org.netbeans.progress.spi.InternalHandle;
057: import org.netbeans.progress.spi.ProgressEvent;
058: import org.netbeans.progress.spi.ProgressUIWorker;
059: import org.netbeans.progress.spi.ProgressUIWorkerProvider;
060: import org.netbeans.progress.spi.ProgressUIWorkerWithModel;
061: import org.netbeans.progress.spi.TaskModel;
062: import org.openide.util.Lookup;
063:
064: /**
065: *
066: * @author Milos Kleint (mkleint@netbeans.org)
067: */
068: public/* final - because of tests */class Controller implements
069: Runnable, ActionListener {
070:
071: // non-private so that it can be accessed from the tests
072: public static Controller defaultInstance;
073:
074: private ProgressUIWorker component;
075: private TaskModel model;
076: private List<ProgressEvent> eventQueue;
077: private boolean dispatchRunning;
078: protected Timer timer;
079: private long timerStart = 0;
080: private static final int TIMER_QUANTUM = 400;
081:
082: /**
083: * initial delay for ading progress indication into the UI. if finishes earlier,
084: * not shown at all, applies just to the status line (default) comtroller.
085: */
086: public static final int INITIAL_DELAY = 500;
087:
088: /** Creates a new instance of Controller */
089: public Controller(ProgressUIWorker comp) {
090: this ();
091: component = comp;
092: }
093:
094: protected Controller() {
095: model = new TaskModel();
096: eventQueue = new ArrayList<ProgressEvent>();
097: dispatchRunning = false;
098: timer = new Timer(TIMER_QUANTUM, this );
099: timer.setRepeats(false);
100: }
101:
102: public static synchronized Controller getDefault() {
103: if (defaultInstance == null) {
104: defaultInstance = new Controller();
105: }
106: return defaultInstance;
107: }
108:
109: // to be called on the default instance only..
110: Component getVisualComponent() {
111: if (component == null) {
112: getProgressUIWorker();
113: }
114: if (component instanceof Component) {
115: return (Component) component;
116: }
117: return null;
118: }
119:
120: ProgressUIWorker getProgressUIWorker() {
121: if (component == null) {
122: ProgressUIWorkerProvider prov = Lookup.getDefault().lookup(
123: ProgressUIWorkerProvider.class);
124: if (prov == null) {
125: Logger
126: .getLogger(Controller.class.getName())
127: .log(Level.CONFIG,
128: "Using fallback trivial progress implementation");
129: prov = new TrivialProgressUIWorkerProvider();
130: }
131: ProgressUIWorkerWithModel prgUIWorker = prov
132: .getDefaultWorker();
133: prgUIWorker.setModel(defaultInstance.getModel());
134: component = prgUIWorker;
135: }
136: return component;
137: }
138:
139: public TaskModel getModel() {
140: return model;
141: }
142:
143: public void start(InternalHandle handle) {
144: ProgressEvent event = new ProgressEvent(handle,
145: ProgressEvent.TYPE_START, isWatched(handle));
146: if (this == getDefault() && handle.getInitialDelay() > 100) {
147: // default controller
148: postEvent(event, true);
149: } else {
150: runImmediately(Collections.singleton(event));
151: }
152: }
153:
154: public void finish(InternalHandle handle) {
155: ProgressEvent event = new ProgressEvent(handle,
156: ProgressEvent.TYPE_FINISH, isWatched(handle));
157: postEvent(event);
158: }
159:
160: public void toIndeterminate(InternalHandle handle) {
161: ProgressEvent event = new ProgressEvent(handle,
162: ProgressEvent.TYPE_SWITCH, isWatched(handle));
163: postEvent(event);
164: }
165:
166: public void toSilent(InternalHandle handle, String message) {
167: ProgressEvent event = new ProgressEvent(handle,
168: ProgressEvent.TYPE_SILENT, isWatched(handle), message);
169: postEvent(event);
170: }
171:
172: public void toDeterminate(InternalHandle handle) {
173: ProgressEvent event = new ProgressEvent(handle,
174: ProgressEvent.TYPE_SWITCH, isWatched(handle));
175: postEvent(event);
176: }
177:
178: public void progress(InternalHandle handle, String msg, int units,
179: double percentage, long estimate) {
180: ProgressEvent event = new ProgressEvent(handle, msg, units,
181: percentage, estimate, isWatched(handle));
182: postEvent(event);
183: }
184:
185: public ProgressEvent snapshot(InternalHandle handle, String msg,
186: int units, double percentage, long estimate) {
187: if (handle.isInSleepMode()) {
188: return new ProgressEvent(handle, ProgressEvent.TYPE_SILENT,
189: isWatched(handle), msg);
190: }
191: return new ProgressEvent(handle, msg, units, percentage,
192: estimate, isWatched(handle));
193: }
194:
195: public void explicitSelection(InternalHandle handle) {
196: InternalHandle old = model.getExplicitSelection();
197: model.explicitlySelect(handle);
198: Collection<ProgressEvent> evnts = new ArrayList<ProgressEvent>();
199: evnts.add(handle.requestStateSnapshot());
200: if (old != null && old != handle) {
201: // refresh the old one, results in un-bodling the text.
202: evnts.add(old.requestStateSnapshot());
203: }
204: runImmediately(evnts);
205: }
206:
207: public void displayNameChange(InternalHandle handle, int units,
208: double percentage, long estimate, String display) {
209: Collection<ProgressEvent> evnts = new ArrayList<ProgressEvent>();
210: evnts.add(new ProgressEvent(handle, null, units, percentage,
211: estimate, isWatched(handle), display));
212: runImmediately(evnts);
213: }
214:
215: private boolean isWatched(InternalHandle hndl) {
216: return model.getExplicitSelection() == hndl;
217: }
218:
219: /**
220: *
221: */
222: void runImmediately(Collection<ProgressEvent> events) {
223: synchronized (this ) {
224: // need to add to queue immediately in the current thread
225: eventQueue.addAll(events);
226: dispatchRunning = true;
227: }
228: // trigger ui update as fast as possible.
229: if (SwingUtilities.isEventDispatchThread()) {
230: run();
231: } else {
232: SwingUtilities.invokeLater(this );
233: }
234:
235: }
236:
237: void postEvent(final ProgressEvent event) {
238: postEvent(event, false);
239: }
240:
241: void postEvent(final ProgressEvent event, boolean shortenPeriod) {
242: synchronized (this ) {
243: eventQueue.add(event);
244: if (!dispatchRunning) {
245: timerStart = System.currentTimeMillis();
246: int delay = timer.getInitialDelay();
247: // period of timer is longer than required by the handle -> shorten it.
248: if (shortenPeriod
249: && timer.getInitialDelay() > event.getSource()
250: .getInitialDelay()) {
251: delay = event.getSource().getInitialDelay();
252: }
253: dispatchRunning = true;
254: resetTimer(delay, true);
255: } else if (shortenPeriod) {
256: // time remaining is longer than required by the handle's initial delay.
257: // restart with shorter time.
258: if (System.currentTimeMillis() - timerStart > event
259: .getSource().getInitialDelay()) {
260: resetTimer(event.getSource().getInitialDelay(),
261: true);
262: }
263: }
264: }
265: }
266:
267: protected void resetTimer(int initialDelay, boolean restart) {
268: timer.setInitialDelay(initialDelay);
269: if (restart) {
270: timer.restart();
271: }
272: }
273:
274: /**
275: * can be run from awt only.
276: */
277: public void run() {
278: HashMap<InternalHandle, ProgressEvent> map = new HashMap<InternalHandle, ProgressEvent>();
279: boolean hasShortOne = false;
280: long minDiff = TIMER_QUANTUM;
281:
282: InternalHandle oldSelected = model.getSelectedHandle();
283: long stamp = System.currentTimeMillis();
284: synchronized (this ) {
285: Iterator<ProgressEvent> it = eventQueue.iterator();
286: Collection<InternalHandle> justStarted = new ArrayList<InternalHandle>();
287: while (it.hasNext()) {
288: ProgressEvent event = it.next();
289: boolean isShort = (stamp - event.getSource()
290: .getTimeStampStarted()) < event.getSource()
291: .getInitialDelay();
292: if (event.getType() == ProgressEvent.TYPE_START) {
293: if (event.getSource().isCustomPlaced() || !isShort) {
294: model.addHandle(event.getSource());
295: } else {
296: justStarted.add(event.getSource());
297: }
298: } else if (event.getType() == ProgressEvent.TYPE_FINISH
299: && (!justStarted.contains(event.getSource()))) {
300: model.removeHandle(event.getSource());
301: }
302: ProgressEvent lastEvent = (ProgressEvent) map.get(event
303: .getSource());
304: if (lastEvent != null
305: && event.getType() == ProgressEvent.TYPE_FINISH
306: && justStarted.contains(event.getSource())
307: && isShort) {
308: // if task quits really fast, ignore..
309: // defined 'really fast' as being shorter than initial delay
310: map.remove(event.getSource());
311: justStarted.remove(event.getSource());
312: } else {
313: if (lastEvent != null) {
314: // preserve last message
315: event.copyMessageFromEarlier(lastEvent);
316: // preserve the switched state
317: if (lastEvent.isSwitched()) {
318: event.markAsSwitched();
319: }
320: }
321: map.put(event.getSource(), event);
322: }
323: it.remove();
324: }
325: // now re-add the just started events into queue
326: // if they don't last longer than the initial delay of the task.
327: // applies just for status bar items
328: Iterator<InternalHandle> startIt = justStarted.iterator();
329: while (startIt.hasNext()) {
330: InternalHandle hndl = startIt.next();
331: long diff = stamp - hndl.getTimeStampStarted();
332: if (diff >= hndl.getInitialDelay()) {
333: model.addHandle(hndl);
334: } else {
335: eventQueue.add(new ProgressEvent(hndl,
336: ProgressEvent.TYPE_START, isWatched(hndl)));
337: ProgressEvent evnt = (ProgressEvent) map
338: .remove(hndl);
339: if (evnt.getType() != ProgressEvent.TYPE_START) {
340: eventQueue.add(evnt);
341: }
342: hasShortOne = true;
343: minDiff = Math.min(minDiff, hndl.getInitialDelay()
344: - diff);
345: }
346: }
347: }
348: InternalHandle selected = model.getSelectedHandle();
349: selected = selected == null ? oldSelected : selected;
350: Iterator<ProgressEvent> it = map.values().iterator();
351: if (component == null) {
352: getProgressUIWorker();
353: }
354: while (it.hasNext()) {
355: ProgressEvent event = it.next();
356: if (selected == event.getSource()) {
357: component.processSelectedProgressEvent(event);
358: }
359: component.processProgressEvent(event);
360: }
361: synchronized (this ) {
362: timer.stop();
363: if (hasShortOne) {
364: timerStart = System.currentTimeMillis();
365: resetTimer((int) Math.max(100, minDiff), true);
366: } else {
367: dispatchRunning = false;
368: resetTimer(TIMER_QUANTUM, false);
369: }
370: }
371: }
372:
373: /**
374: * used by Timer
375: */
376: public void actionPerformed(java.awt.event.ActionEvent actionEvent) {
377: run();
378: }
379:
380: }
|