/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.sdkuilib.ui; import com.android.SdkConstants; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import java.util.HashMap; import java.util.Map; /** * A base class for an SWT Dialog. * <p/> * The base class offers the following goodies: <br/> * - Dialog is automatically centered on its parent. <br/> * - Dialog size is reused during the session. <br/> * - A simple API with an {@link #open()} method that returns a boolean. <br/> * <p/> * A typical usage is: * <pre> * MyDialog extends SwtBaseDialog { ... } * MyDialog d = new MyDialog(parentShell, "My Dialog Title"); * if (d.open()) { * ...do something like refresh parent list view * } * </pre> * We also have a JFace-base {@link GridDialog}. * The JFace dialog is good when you just want a typical OK/Cancel layout with the * buttons all managed for you. * This SWT base dialog has little decoration. * It's up to you to manage whatever buttons you want, if any. */ public abstract class SwtBaseDialog extends Dialog { /** * Min Y location for dialog. Need to deal with the menu bar on mac os. */ private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 20 : 0; /** Last dialog size for this session, different for each dialog class. */ private static Map<Class<?>, Point> sLastSizeMap = new HashMap<Class<?>, Point>(); private volatile boolean mQuitRequested = false; private boolean mReturnValue; private Shell mShell; /** * Create the dialog. * * @param parent The parent's shell * @param title The dialog title. Can be null. */ public SwtBaseDialog(Shell parent, int swtStyle, String title) { super(parent, swtStyle); if (title != null) { setText(title); } } /** * Open the dialog. * * @return The last value set using {@link #setReturnValue(boolean)} or false by default. */ public boolean open() { if (!mQuitRequested) { createShell(); } if (!mQuitRequested) { createContents(); } if (!mQuitRequested) { positionShell(); } if (!mQuitRequested) { postCreate(); } if (!mQuitRequested) { mShell.open(); mShell.layout(); eventLoop(); } return mReturnValue; } /** * Creates the shell for this dialog. * The default shell has a size of 450x300, which is also its minimum size. * You might want to override these values. * <p/> * Called before {@link #createContents()}. */ protected void createShell() { mShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.APPLICATION_MODAL); mShell.setMinimumSize(new Point(450, 300)); mShell.setSize(450, 300); if (getText() != null) { mShell.setText(getText()); } mShell.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { saveSize(); } }); } /** * Creates the content and attaches it to the current shell (cf. {@link #getShell()}). * <p/> * Derived classes should consider creating the UI here and initializing their * state in {@link #postCreate()}. */ protected abstract void createContents(); /** * Called after {@link #createContents()} and after {@link #positionShell()} * just before the dialog is actually shown on screen. * <p/> * Derived classes should consider creating the UI in {@link #createContents()} and * initialize it here. */ protected abstract void postCreate(); /** * Run the event loop. * This is called from {@link #open()} after {@link #postCreate()} and * after the window has been shown on screen. * Derived classes might want to use this as a place to start automated * tasks that will update the UI. */ protected void eventLoop() { Display display = getParent().getDisplay(); while (!mQuitRequested && !mShell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * Returns the current value that {@link #open()} will return to the caller. * Default is false. */ protected boolean getReturnValue() { return mReturnValue; } /** * Sets the value that {@link #open()} will return to the caller. * @param returnValue The new value to be returned by {@link #open()}. */ protected void setReturnValue(boolean returnValue) { mReturnValue = returnValue; } /** * Returns the shell created by {@link #createShell()}. * @return The current {@link Shell}. */ protected Shell getShell() { return mShell; } /** * Saves the dialog size and close the dialog. * The {@link #open()} method will given return value (see {@link #setReturnValue(boolean)}. * <p/> * It's safe to call this method before the shell is initialized, * in which case the dialog will close as soon as possible. */ protected void close() { if (mShell != null && !mShell.isDisposed()) { saveSize(); getShell().close(); } mQuitRequested = true; } //------- /** * Centers the dialog in its parent shell. */ private void positionShell() { // Centers the dialog in its parent shell Shell child = mShell; Shell parent = getParent(); if (child != null && parent != null) { // get the parent client area with a location relative to the display Rectangle parentArea = parent.getClientArea(); Point parentLoc = parent.getLocation(); int px = parentLoc.x; int py = parentLoc.y; int pw = parentArea.width; int ph = parentArea.height; // Reuse the last size if there's one, otherwise use the default Point childSize = sLastSizeMap.get(this.getClass()); if (childSize == null) { childSize = child.getSize(); } int cw = childSize.x; int ch = childSize.y; int x = px + (pw - cw) / 2; if (x < 0) x = 0; int y = py + (ph - ch) / 2; if (y < MIN_Y) y = MIN_Y; child.setLocation(x, y); child.setSize(cw, ch); } } private void saveSize() { if (mShell != null && !mShell.isDisposed()) { sLastSizeMap.put(this.getClass(), mShell.getSize()); } } }