001: /* uDig - User Friendly Desktop Internet GIS client
002: * http://udig.refractions.net
003: * (C) 2004, Refractions Research Inc.
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation;
008: * version 2.1 of the License.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: */
015: package net.refractions.udig.ui;
016:
017: import net.refractions.udig.internal.ui.UiPlugin;
018:
019: import org.eclipse.jface.dialogs.Dialog;
020: import org.eclipse.jface.dialogs.IDialogConstants;
021: import org.eclipse.jface.dialogs.MessageDialog;
022: import org.eclipse.jface.window.IShellProvider;
023: import org.eclipse.swt.SWT;
024: import org.eclipse.swt.graphics.Image;
025: import org.eclipse.swt.graphics.Point;
026: import org.eclipse.swt.graphics.Rectangle;
027: import org.eclipse.swt.widgets.Composite;
028: import org.eclipse.swt.widgets.Control;
029: import org.eclipse.swt.widgets.Display;
030: import org.eclipse.swt.widgets.Event;
031: import org.eclipse.swt.widgets.Listener;
032: import org.eclipse.swt.widgets.Shell;
033: import org.eclipse.swt.widgets.TreeItem;
034:
035: /**
036: * A dialog that, on opening, will zoom from the start location/size the location of the provided dialog.
037: * <p>
038: * IMPORTANT: Since there is no way for ZoomingDialog to determine whether setBlockOnOpen is
039: * set on the wrapped/decorated Dialog setBlockOnOpen <em>MUST</em> be set on ZoomingDialog
040: * </p>
041: * @author Jesse
042: * @since 1.1.0
043: */
044: public class ZoomingDialog extends Dialog {
045: public static final int FAST = 1;
046: public static final int MEDIUM = 4;
047: public static final int SLOW = 8;
048: private static final int BASE_NUMBER_STEPS = 15;
049: private final Dialog delegate;
050: private final Point size;
051: private final Point location;
052: private Rectangle end;
053: private int zoomSpeed = FAST;
054: private boolean shouldBlock;
055:
056: /**
057: * Creates a new instance.
058: * @param parentShell shell to use as a parent
059: * @param delegate The dialog to open
060: * @param start the rectangle, in Display coordinates, to zoom from when opening.
061: */
062: public ZoomingDialog(Shell parentShell, Dialog delegate,
063: Rectangle start) {
064: super (new ZoomShellProvider(parentShell));
065: this .delegate = delegate;
066: this .location = new Point(start.x, start.y);
067: this .size = new Point(start.width, start.height);
068: setShellStyle(SWT.ON_TOP | SWT.NO_FOCUS | SWT.NO_TRIM
069: | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE);
070: }
071:
072: /**
073: * Create new instance. Will zoom from the provided rectangle to the dialog location.
074: *
075: * @param parentShell shell to use as a parent
076: * @param dialog dialog to open
077: * @param x
078: * @param y
079: * @param width
080: * @param height
081: */
082: public ZoomingDialog(Shell parentShell, Dialog dialog, int x,
083: int y, int width, int height) {
084: this (parentShell, dialog, new Rectangle(x, y, width, height));
085: }
086:
087: /**
088: * Sets how long it takes for the Dialog to open, default is {@link #FAST}. Is one of
089: *
090: * {@link #FAST}
091: * {@link #MEDIUM}
092: * {@link #SLOW}
093: *
094: * @param speed a constant indicating the speed at which the dialog zooms
095: */
096: public void setZoomSpeed(int speed) {
097: this .zoomSpeed = speed;
098: }
099:
100: @Override
101: public void setBlockOnOpen(boolean shouldBlock) {
102: this .shouldBlock = shouldBlock;
103: }
104:
105: public boolean close() {
106:
107: boolean result = delegate.close();
108: return result;
109: }
110:
111: private void closeInternal() {
112: super .getShell().setVisible(true);
113: delegate.getShell().setVisible(false);
114: zoom(false);
115: super .close();
116: }
117:
118: public void create() {
119: super .create();
120: if (delegate.getShell() == null)
121: delegate.create();
122: }
123:
124: public int getReturnCode() {
125: return delegate.getReturnCode();
126: }
127:
128: @Override
129: protected Point getInitialLocation(Point initialSize) {
130: return location;
131: }
132:
133: @Override
134: protected Point getInitialSize() {
135: return size;
136: }
137:
138: /**
139: * Opens the dialog.
140: *
141: * <p>
142: * IMPORTANT: Since there is no way for ZoomingDialog to determine whether setBlockOnOpen is
143: * set on the wrapped/decorated Dialog setBlockOnOpen <em>MUST</em> be set on ZoomingDialog
144: * </p>
145: *
146: */
147: public int open() {
148: if (delegate.getShell() == null)
149: delegate.create();
150:
151: end = delegate.getShell().getBounds();
152: // end=new Rectangle(300, 300, 800,600);
153: super .setBlockOnOpen(false);
154: super .open();
155:
156: zoom(true);
157:
158: // Must add listeners after delegate is open.
159: // Therefore blocking must be false on the delegate so that
160: // listeners can be added.
161: delegate.setBlockOnOpen(false);
162: int open = delegate.open();
163: addClosingListeners();
164:
165: if (shouldBlock)
166: runEventLoop(getShell());
167: return open;
168: }
169:
170: private void runEventLoop(Shell loopShell) {
171:
172: //Use the display provided by the shell if possible
173: Display display;
174: if (getShell() == null) {
175: display = Display.getCurrent();
176: } else {
177: display = loopShell.getDisplay();
178: }
179:
180: while (loopShell != null && !loopShell.isDisposed()) {
181: try {
182: if (!display.readAndDispatch()) {
183: display.sleep();
184: }
185: } catch (Throwable e) {
186: UiPlugin.log("Exception in UI thread while waiting", e); //$NON-NLS-1$
187: }
188: }
189: display.update();
190: }
191:
192: private void addClosingListeners() {
193: delegate.getShell().addListener(SWT.Close | SWT.Dispose,
194: new Listener() {
195:
196: public void handleEvent(Event event) {
197: closeInternal();
198: }
199:
200: });
201: delegate.getShell().addListener(SWT.Dispose, new Listener() {
202:
203: public void handleEvent(Event event) {
204: closeInternal();
205: }
206:
207: });
208: }
209:
210: private void zoom(boolean grow) {
211: Point location = this .location;
212: Point size = this .size;
213: int totalSteps = BASE_NUMBER_STEPS * zoomSpeed;
214: int xstep = (end.x - location.x) / totalSteps;
215: int ystep = (end.y - location.y) / totalSteps;
216: int xsize = (end.width - size.x) / totalSteps;
217: int ysize = (end.height - size.y) / totalSteps;
218: if (grow) {
219: int steps = 0;
220: while (steps < totalSteps) {
221: int nextX = location.x + steps * xstep;
222: int nextY = location.y + steps * ystep;
223: int nextWidth = size.x + steps * xsize;
224: int nextHeight = size.y + steps * ysize;
225: super .getShell().setBounds(
226: new Rectangle(nextX, nextY, nextWidth,
227: nextHeight));
228: steps++;
229: }
230: } else {
231: int steps = totalSteps;
232: while (steps > -1) {
233: int nextX = location.x + steps * xstep;
234: int nextY = location.y + steps * ystep;
235: int nextWidth = size.x + steps * xsize;
236: int nextHeight = size.y + steps * ysize;
237: super .getShell().setBounds(
238: new Rectangle(nextX, nextY, nextWidth,
239: nextHeight));
240: steps--;
241: }
242: }
243: super .getShell().setVisible(false);
244:
245: }
246:
247: public String toString() {
248: return delegate.toString();
249: }
250:
251: @Override
252: protected Control createButtonBar(Composite parent) {
253: return new Composite(parent, SWT.NONE);
254: }
255:
256: /**
257: * Create a message dialog. Notethat the dialog will have no visual
258: * representation (no widgets) until it is told to open.
259: * <p>
260: * The labels of the buttons to appear in the button bar are supplied in
261: * this constructor as an array. The <code>open</code> method will return
262: * the index of the label in this array corresponding to the button that was
263: * pressed to close the dialog. If the dialog was dismissed without pressing
264: * a button (ESC, etc.) then -1 is returned. Note that the <code>open</code>
265: * method blocks.
266: * </p>
267: *
268: * @param start
269: * the location to zoom from.
270: * @param parentShell
271: * the parent shell
272: * @param dialogTitle
273: * the dialog title, or <code>null</code> if none
274: * @param dialogTitleImage
275: * the dialog title image, or <code>null</code> if none
276: * @param dialogMessage
277: * the dialog message
278: * @param dialogImageType
279: * one of the following values:
280: * <ul>
281: * <li><code>MessageDialog.NONE</code> for a dialog with no
282: * image</li>
283: * <li><code>MessageDialog.ERROR</code> for a dialog with an
284: * error image</li>
285: * <li><code>MessageDialog.INFORMATION</code> for a dialog
286: * with an information image</li>
287: * <li><code>MessageDialog.QUESTION </code> for a dialog with a
288: * question image</li>
289: * <li><code>MessageDialog.WARNING</code> for a dialog with a
290: * warning image</li>
291: * </ul>
292: * @param dialogButtonLabels
293: * an array of labels for the buttons in the button bar
294: * @param defaultIndex
295: * the index in the button label array of the default button
296: * @return
297: */
298: public static int openMessageDialog(Rectangle start,
299: Shell parentShell, String dialogTitle, Image dialogImage,
300: String dialogMessage, int dialogImageType,
301: String[] buttonLabels, int defaultIndex) {
302:
303: MessageDialog dialog = new MessageDialog(parentShell,
304: dialogTitle, dialogImage, dialogMessage,
305: dialogImageType, buttonLabels, defaultIndex);
306: ZoomingDialog zd = new ZoomingDialog(parentShell, dialog, start);
307:
308: zd.open();
309: return zd.getReturnCode();
310: }
311:
312: public static void openErrorMessage(Rectangle start,
313: Shell parentShell, String dialogTitle, String dialogMessage) {
314: openMessageDialog(start, parentShell, dialogTitle, null,
315: dialogMessage, MessageDialog.ERROR,
316: new String[] { IDialogConstants.OK_LABEL }, 1);
317: }
318:
319: public static void openWarningMessage(Rectangle start,
320: Shell parentShell, String dialogTitle, String dialogMessage) {
321: openMessageDialog(start, parentShell, dialogTitle, null,
322: dialogMessage, MessageDialog.WARNING,
323: new String[] { IDialogConstants.OK_LABEL }, 1);
324: }
325:
326: public static void openInformationMessage(Rectangle start,
327: Shell parentShell, String dialogTitle, String dialogMessage) {
328: openMessageDialog(start, parentShell, dialogTitle, null,
329: dialogMessage, MessageDialog.INFORMATION,
330: new String[] { IDialogConstants.OK_LABEL }, 1);
331: }
332:
333: public static boolean openQuestionMessage(Rectangle start,
334: Shell parentShell, String dialogTitle, String dialogMessage) {
335:
336: int result = openMessageDialog(start, parentShell, dialogTitle,
337: null, dialogMessage, MessageDialog.QUESTION,
338: new String[] { IDialogConstants.YES_LABEL,
339: IDialogConstants.NO_LABEL }, 1);
340: return result == IDialogConstants.YES_ID;
341: }
342:
343: /**
344: * Calculates the bounds of the Control in Display coordinates (Required by ZoomingDialog constructor).
345: *
346: * @param control control to use as the starting position
347: * @return the bounds of the Control in Display coordinates
348: */
349: public static Rectangle calculateBounds(Control control) {
350: Point ul = control.toDisplay(0, 0);
351: Point size2 = control.getSize();
352: Rectangle start = new Rectangle(ul.x, ul.y, size2.x, size2.y);
353: return start;
354: }
355:
356: /**
357: * Calculates the bounds of the Control in Display coordinates (Required by ZoomingDialog constructor).
358: *
359: * @param item TreeItem to use as the starting position
360: * @param columnIndex the index of the column to find the bounds for. If -1 bounds of entire item are found
361: * @return the bounds of the Control in Display coordinates
362: */
363: public static Rectangle calculateBounds(TreeItem item,
364: int columnIndex) {
365: Point ulTree = item.getParent().toDisplay(0, 0);
366: Rectangle bounds;
367: if (columnIndex > -1) {
368: bounds = item.getBounds(columnIndex);
369: bounds.x += ulTree.x;
370: bounds.y += ulTree.y;
371: } else {
372: Rectangle bounds2 = item.getBounds(0);
373: bounds = new Rectangle(ulTree.x, ulTree.y, 0, 0);
374: bounds.width = item.getParent().getSize().x;
375: bounds.height = bounds2.height;
376: }
377:
378: return bounds;
379: }
380:
381: private static class ZoomShellProvider implements IShellProvider {
382:
383: private Shell template;
384:
385: public ZoomShellProvider(Shell shell) {
386: this .template = shell;
387: }
388:
389: public Shell getShell() {
390:
391: Shell shell;
392: if (template == null)
393: shell = new Shell();
394: else
395: shell = new Shell(template.getDisplay());
396: return shell;
397: }
398:
399: }
400:
401: }
|