001: /*******************************************************************************
002: * Copyright (c) 2004, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal;
011:
012: import org.eclipse.core.runtime.IProgressMonitor;
013: import org.eclipse.core.runtime.IStatus;
014: import org.eclipse.core.runtime.Status;
015: import org.eclipse.core.runtime.jobs.Job;
016: import org.eclipse.jface.preference.IPreferenceStore;
017: import org.eclipse.jface.util.Geometry;
018: import org.eclipse.swt.graphics.GC;
019: import org.eclipse.swt.graphics.Image;
020: import org.eclipse.swt.graphics.Rectangle;
021: import org.eclipse.swt.widgets.Control;
022: import org.eclipse.swt.widgets.Display;
023: import org.eclipse.swt.widgets.Shell;
024: import org.eclipse.ui.IWorkbenchPreferenceConstants;
025: import org.eclipse.ui.internal.util.PrefUtil;
026:
027: /**
028: * This job creates an animated rectangle that moves from a source rectangle to
029: * a target in a fixed amount of time. To begin the animation, instantiate this
030: * object then call schedule().
031: *
032: * @since 3.0
033: */
034: public class RectangleAnimation extends Job {
035: private static class AnimationFeedbackFactory {
036: /**
037: * Determines whether or not the system being used is
038: * sufficiently fast to support image animations.
039: *
040: * Assumes that a pixel is ~3 bytes
041: *
042: * For now we use a base limitation of 50MB/sec as a
043: * 'reverse blt' rate so that a 2MB size shell can be
044: * captured in under 1/25th of a sec.
045: */
046: private static final int IMAGE_ANIMATION_THRESHOLD = 25; // Frame captures / Sec
047: private static final int IMAGE_ANIMATION_TEST_LOOP_COUNT = 20; // test the copy 'n' times
048:
049: //private static double framesPerSec = 0.0;
050:
051: public static double getCaptureSpeed(Shell wb) {
052: // OK, capture
053: Rectangle bb = wb.getBounds();
054: Image backingStore = new Image(wb.getDisplay(), bb);
055: GC gc = new GC(wb);
056:
057: // Loop 'n' times to average the result
058: long startTime = System.currentTimeMillis();
059: for (int i = 0; i < IMAGE_ANIMATION_TEST_LOOP_COUNT; i++)
060: gc.copyArea(backingStore, bb.x, bb.y);
061: gc.dispose();
062: long endTime = System.currentTimeMillis();
063:
064: // get Frames / Sec
065: double fps = IMAGE_ANIMATION_TEST_LOOP_COUNT
066: / ((endTime - startTime) / 1000.0);
067: double pps = fps * (bb.width * bb.height * 4); // 4 bytes/pixel
068: System.out
069: .println("FPS: " + fps + " Bytes/sec: " + (long) pps); //$NON-NLS-1$ //$NON-NLS-2$
070: return fps;
071: }
072:
073: public boolean useImageAnimations(Shell wb) {
074: return getCaptureSpeed(wb) >= IMAGE_ANIMATION_THRESHOLD;
075: }
076:
077: public static DefaultAnimationFeedback createAnimationRenderer(
078: Shell parentShell) {
079: // on the first call test the animation threshold to determine
080: // whether to use image animations or not...
081: // if (framesPerSec == 0.0)
082: // framesPerSec = getCaptureSpeed(parentShell);
083: //
084: // IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore();
085: // boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX);
086: //
087: // if (useNewMinMax && framesPerSec >= IMAGE_ANIMATION_THRESHOLD) {
088: // return new ImageAnimationFeedback();
089: // }
090: //
091: return new DefaultAnimationFeedback();
092: }
093: }
094:
095: // Constants
096: public static final int TICK_TIMER = 1;
097: public static final int FRAME_COUNT = 2;
098:
099: // Animation Parameters
100: private Display display;
101:
102: private boolean enableAnimations;
103: private int timingStyle = TICK_TIMER;
104: private int duration;
105:
106: // Control State
107: private DefaultAnimationFeedback feedbackRenderer;
108: private long stepCount;
109: private long frameCount;
110: private long startTime;
111: private long curTime;
112: private long prevTime;
113:
114: // Macros
115: private boolean done() {
116: return amount() >= 1.0;
117: }
118:
119: public static Rectangle interpolate(Rectangle start, Rectangle end,
120: double amount) {
121: double initialWeight = 1.0 - amount;
122:
123: Rectangle result = new Rectangle(
124: (int) (start.x * initialWeight + end.x * amount),
125: (int) (start.y * initialWeight + end.y * amount),
126: (int) (start.width * initialWeight + end.width * amount),
127: (int) (start.height * initialWeight + end.height
128: * amount));
129:
130: return result;
131: }
132:
133: // Animation Step
134: private Runnable animationStep = new Runnable() {
135:
136: public void run() {
137: // Capture time
138: prevTime = curTime;
139: curTime = System.currentTimeMillis();
140:
141: // Has the system timer 'ticked'?
142: if (curTime != prevTime) {
143: clockTick();
144: }
145:
146: if (isUpdateStep()) {
147: updateDisplay();
148: frameCount++;
149: }
150:
151: stepCount++;
152: }
153:
154: };
155:
156: /**
157: * Creates an animation that will morph the start rectangle to the end rectangle in the
158: * given number of milliseconds. The animation will take the given number of milliseconds to
159: * complete.
160: *
161: * Note that this is a Job, so you must invoke schedule() before the animation will begin
162: *
163: * @param whereToDraw specifies the composite where the animation will be drawn. Note that
164: * although the start and end rectangles can accept any value in display coordinates, the
165: * actual animation will be clipped to the boundaries of this composite. For this reason,
166: * it is good to select a composite that encloses both the start and end rectangles.
167: * @param start initial rectangle (display coordinates)
168: * @param end final rectangle (display coordinates)
169: * @param duration number of milliseconds over which the animation will run
170: */
171: public RectangleAnimation(Shell parentShell, Rectangle start,
172: Rectangle end, int duration) {
173: super (WorkbenchMessages.RectangleAnimation_Animating_Rectangle);
174:
175: // if animations aren't on this is a NO-OP
176: IPreferenceStore preferenceStore = PrefUtil
177: .getAPIPreferenceStore();
178: enableAnimations = preferenceStore
179: .getBoolean(IWorkbenchPreferenceConstants.ENABLE_ANIMATIONS);
180:
181: if (!enableAnimations) {
182: return;
183: }
184:
185: // Capture paraeters
186: display = parentShell.getDisplay();
187: this .duration = duration;
188:
189: // Don't show the job in monitors
190: setSystem(true);
191:
192: // Pick the renderer (could be a preference...)
193: feedbackRenderer = AnimationFeedbackFactory
194: .createAnimationRenderer(parentShell);
195:
196: // Set it up
197: feedbackRenderer.initialize(parentShell, start, end);
198:
199: // Set the animation's initial state
200: stepCount = 0;
201: //long totalFrames = (long) ((duration / 1000.0) * framesPerSec);
202: curTime = startTime = System.currentTimeMillis();
203: }
204:
205: public RectangleAnimation(Shell parentShell, Rectangle start,
206: Rectangle end) {
207: this (parentShell, start, end, 400);
208: }
209:
210: public void addStartRect(Rectangle rect) {
211: if (feedbackRenderer != null)
212: feedbackRenderer.addStartRect(rect);
213: }
214:
215: public void addEndRect(Rectangle rect) {
216: if (feedbackRenderer != null)
217: feedbackRenderer.addEndRect(rect);
218: }
219:
220: public void addStartRect(Control ctrl) {
221: Rectangle ctrlBounds = ctrl.getBounds();
222: Rectangle startRect = Geometry.toDisplay(ctrl.getParent(),
223: ctrlBounds);
224: addStartRect(startRect);
225: }
226:
227: public void addEndRect(Control ctrl) {
228: Rectangle ctrlBounds = ctrl.getBounds();
229: Rectangle endRect = Geometry.toDisplay(ctrl.getParent(),
230: ctrlBounds);
231: addEndRect(endRect);
232: }
233:
234: /**
235: *
236: */
237: protected void clockTick() {
238: }
239:
240: /**
241: * @return
242: */
243: protected boolean isUpdateStep() {
244: switch (timingStyle) {
245: case TICK_TIMER:
246: return prevTime != curTime;
247:
248: case FRAME_COUNT:
249: return true;
250: }
251:
252: return false;
253: }
254:
255: private double amount() {
256: double amount = 0.0;
257:
258: switch (timingStyle) {
259: case TICK_TIMER:
260: amount = (double) (curTime - startTime) / (double) duration;
261: break;
262:
263: case FRAME_COUNT:
264: amount = (double) frameCount / (double) duration;
265: }
266:
267: if (amount > 1.0)
268: amount = 1.0;
269:
270: return amount;
271: }
272:
273: /**
274: *
275: */
276: protected void updateDisplay() {
277: feedbackRenderer.renderStep(amount());
278: }
279:
280: /* (non-Javadoc)
281: * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
282: */
283: protected IStatus run(IProgressMonitor monitor) {
284:
285: // We use preference value to indicate that the animation should be skipped on this platform.
286: if (!enableAnimations || feedbackRenderer == null) {
287: return Status.OK_STATUS;
288: }
289:
290: // Do we have anything to animate ?
291: boolean isEmpty = feedbackRenderer.getStartRects().size() == 0;
292: if (isEmpty) {
293: return Status.OK_STATUS;
294: }
295:
296: // We're starting, initialize
297: display.syncExec(new Runnable() {
298: public void run() {
299: feedbackRenderer.jobInit();
300: }
301: });
302:
303: // Only start the animation timer -after- we've initialized
304: curTime = startTime = System.currentTimeMillis();
305:
306: while (!done()) {
307: display.syncExec(animationStep);
308: // Don't pin the CPU
309: Thread.yield();
310: }
311:
312: //System.out.println("Done: " + (curTime-startTime) + " steps: " + stepCount + " frames:" + frameCount); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
313: // We're done, clean up
314: display.syncExec(new Runnable() {
315: public void run() {
316: feedbackRenderer.dispose();
317: }
318: });
319:
320: return Status.OK_STATUS;
321: }
322: }
|