1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.sdkuilib.ui; 18 19import com.android.SdkConstants; 20 21import org.eclipse.swt.SWT; 22import org.eclipse.swt.events.DisposeEvent; 23import org.eclipse.swt.events.DisposeListener; 24import org.eclipse.swt.graphics.Point; 25import org.eclipse.swt.graphics.Rectangle; 26import org.eclipse.swt.widgets.Dialog; 27import org.eclipse.swt.widgets.Display; 28import org.eclipse.swt.widgets.Shell; 29 30import java.util.HashMap; 31import java.util.Map; 32 33/** 34 * A base class for an SWT Dialog. 35 * <p/> 36 * The base class offers the following goodies: <br/> 37 * - Dialog is automatically centered on its parent. <br/> 38 * - Dialog size is reused during the session. <br/> 39 * - A simple API with an {@link #open()} method that returns a boolean. <br/> 40 * <p/> 41 * A typical usage is: 42 * <pre> 43 * MyDialog extends SwtBaseDialog { ... } 44 * MyDialog d = new MyDialog(parentShell, "My Dialog Title"); 45 * if (d.open()) { 46 * ...do something like refresh parent list view 47 * } 48 * </pre> 49 * We also have a JFace-base {@link GridDialog}. 50 * The JFace dialog is good when you just want a typical OK/Cancel layout with the 51 * buttons all managed for you. 52 * This SWT base dialog has little decoration. 53 * It's up to you to manage whatever buttons you want, if any. 54 */ 55public abstract class SwtBaseDialog extends Dialog { 56 57 /** 58 * Min Y location for dialog. Need to deal with the menu bar on mac os. 59 */ 60 private final static int MIN_Y = 61 SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 20 : 0; 62 63 /** Last dialog size for this session, different for each dialog class. */ 64 private static Map<Class<?>, Point> sLastSizeMap = new HashMap<Class<?>, Point>(); 65 66 private volatile boolean mQuitRequested = false; 67 private boolean mReturnValue; 68 private Shell mShell; 69 70 /** 71 * Create the dialog. 72 * 73 * @param parent The parent's shell 74 * @param title The dialog title. Can be null. 75 */ 76 public SwtBaseDialog(Shell parent, int swtStyle, String title) { 77 super(parent, swtStyle); 78 if (title != null) { 79 setText(title); 80 } 81 } 82 83 /** 84 * Open the dialog. 85 * 86 * @return The last value set using {@link #setReturnValue(boolean)} or false by default. 87 */ 88 public boolean open() { 89 if (!mQuitRequested) { 90 createShell(); 91 } 92 if (!mQuitRequested) { 93 createContents(); 94 } 95 if (!mQuitRequested) { 96 positionShell(); 97 } 98 if (!mQuitRequested) { 99 postCreate(); 100 } 101 if (!mQuitRequested) { 102 mShell.open(); 103 mShell.layout(); 104 eventLoop(); 105 } 106 107 return mReturnValue; 108 } 109 110 /** 111 * Creates the shell for this dialog. 112 * The default shell has a size of 450x300, which is also its minimum size. 113 * You might want to override these values. 114 * <p/> 115 * Called before {@link #createContents()}. 116 */ 117 protected void createShell() { 118 mShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.APPLICATION_MODAL); 119 mShell.setMinimumSize(new Point(450, 300)); 120 mShell.setSize(450, 300); 121 if (getText() != null) { 122 mShell.setText(getText()); 123 } 124 mShell.addDisposeListener(new DisposeListener() { 125 @Override 126 public void widgetDisposed(DisposeEvent e) { 127 saveSize(); 128 } 129 }); 130 } 131 132 /** 133 * Creates the content and attaches it to the current shell (cf. {@link #getShell()}). 134 * <p/> 135 * Derived classes should consider creating the UI here and initializing their 136 * state in {@link #postCreate()}. 137 */ 138 protected abstract void createContents(); 139 140 /** 141 * Called after {@link #createContents()} and after {@link #positionShell()} 142 * just before the dialog is actually shown on screen. 143 * <p/> 144 * Derived classes should consider creating the UI in {@link #createContents()} and 145 * initialize it here. 146 */ 147 protected abstract void postCreate(); 148 149 /** 150 * Run the event loop. 151 * This is called from {@link #open()} after {@link #postCreate()} and 152 * after the window has been shown on screen. 153 * Derived classes might want to use this as a place to start automated 154 * tasks that will update the UI. 155 */ 156 protected void eventLoop() { 157 Display display = getParent().getDisplay(); 158 while (!mQuitRequested && !mShell.isDisposed()) { 159 if (!display.readAndDispatch()) { 160 display.sleep(); 161 } 162 } 163 } 164 165 /** 166 * Returns the current value that {@link #open()} will return to the caller. 167 * Default is false. 168 */ 169 protected boolean getReturnValue() { 170 return mReturnValue; 171 } 172 173 /** 174 * Sets the value that {@link #open()} will return to the caller. 175 * @param returnValue The new value to be returned by {@link #open()}. 176 */ 177 protected void setReturnValue(boolean returnValue) { 178 mReturnValue = returnValue; 179 } 180 181 /** 182 * Returns the shell created by {@link #createShell()}. 183 * @return The current {@link Shell}. 184 */ 185 protected Shell getShell() { 186 return mShell; 187 } 188 189 /** 190 * Saves the dialog size and close the dialog. 191 * The {@link #open()} method will given return value (see {@link #setReturnValue(boolean)}. 192 * <p/> 193 * It's safe to call this method before the shell is initialized, 194 * in which case the dialog will close as soon as possible. 195 */ 196 protected void close() { 197 if (mShell != null && !mShell.isDisposed()) { 198 saveSize(); 199 getShell().close(); 200 } 201 mQuitRequested = true; 202 } 203 204 //------- 205 206 /** 207 * Centers the dialog in its parent shell. 208 */ 209 private void positionShell() { 210 // Centers the dialog in its parent shell 211 Shell child = mShell; 212 Shell parent = getParent(); 213 if (child != null && parent != null) { 214 // get the parent client area with a location relative to the display 215 Rectangle parentArea = parent.getClientArea(); 216 Point parentLoc = parent.getLocation(); 217 int px = parentLoc.x; 218 int py = parentLoc.y; 219 int pw = parentArea.width; 220 int ph = parentArea.height; 221 222 // Reuse the last size if there's one, otherwise use the default 223 Point childSize = sLastSizeMap.get(this.getClass()); 224 if (childSize == null) { 225 childSize = child.getSize(); 226 } 227 int cw = childSize.x; 228 int ch = childSize.y; 229 230 int x = px + (pw - cw) / 2; 231 if (x < 0) x = 0; 232 233 int y = py + (ph - ch) / 2; 234 if (y < MIN_Y) y = MIN_Y; 235 236 child.setLocation(x, y); 237 child.setSize(cw, ch); 238 } 239 } 240 241 private void saveSize() { 242 if (mShell != null && !mShell.isDisposed()) { 243 sLastSizeMap.put(this.getClass(), mShell.getSize()); 244 } 245 } 246 247} 248