| /******************************************************************************* |
| * Copyright (c) 2008 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.ui.internal.util; |
| |
| import java.util.Locale; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.AssertionFailedException; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jpt.ui.internal.widgets.NullPostExecution; |
| import org.eclipse.jpt.ui.internal.widgets.PostExecution; |
| import org.eclipse.jpt.utility.internal.ClassTools; |
| import org.eclipse.swt.custom.CCombo; |
| import org.eclipse.swt.events.FocusEvent; |
| import org.eclipse.swt.events.FocusListener; |
| import org.eclipse.swt.events.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.widgets.Combo; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.forms.widgets.ScrolledForm; |
| |
| /** |
| * A suite of utility methods related to the user interface. |
| * |
| * @version 2.0 |
| * @since 1.0 |
| */ |
| @SuppressWarnings("nls") |
| public class SWTUtil { |
| |
| /** |
| * Causes the <code>run()</code> method of the given runnable to be invoked |
| * by the user-interface thread at the next reasonable opportunity. The caller |
| * of this method continues to run in parallel, and is not notified when the |
| * runnable has completed. |
| * |
| * @param runnable Code to run on the user-interface thread |
| * @exception org.eclipse.swt.SWTException |
| * <ul> |
| * <li>ERROR_DEVICE_DISPOSED - if the receiver has been disposed</li> |
| * </ul> |
| * @see #syncExec |
| */ |
| public static void asyncExec(Runnable runnable) { |
| getStandardDisplay().asyncExec(runnable); |
| } |
| |
| /** |
| * Tweaks the given <code>CCombo</code> to remove the default value when the |
| * widget receives the focus and to show the default when the widget loses |
| * the focus. |
| * |
| * @param combo The widget having a default value that is always at the |
| * beginning of the list |
| */ |
| public static void attachDefaultValueHandler(CCombo combo) { |
| CComboHandler handler = new CComboHandler(); |
| combo.addFocusListener(handler); |
| combo.addModifyListener(handler); |
| } |
| |
| /** |
| * Tweaks the given <code>Combo</code> to remove the default value when the |
| * widget receives the focus and to show the default when the widget loses |
| * the focus. |
| * |
| * @param combo The widget having a default value that is always at the |
| * beginning of the list |
| */ |
| public static void attachDefaultValueHandler(Combo combo) { |
| ComboHandler handler = new ComboHandler(); |
| combo.addFocusListener(handler); |
| combo.addModifyListener(handler); |
| } |
| |
| /** |
| * Retrieves the localized string from the given NLS class by creating the |
| * key. That key is the concatenation of the composite's short class name |
| * with the toString() of the given value separated by an underscore. |
| * |
| * @param nlsClass The NLS class used to retrieve the localized text |
| * @param compositeClass The class used for creating the key, its short class |
| * name is the beginning of the key |
| * @param value The value used to append its toString() to the generated key |
| * @return The localized text associated with the value |
| */ |
| public static String buildDisplayString(Class<?> nlsClass, |
| Class<?> compositeClass, |
| Object value) { |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append(ClassTools.shortNameFor(compositeClass)); |
| sb.append("_"); |
| sb.append(value.toString().toLowerCase(Locale.ENGLISH));//bug 234953 |
| //TODO in a future release we should not be converting the key using toLowerCase() |
| |
| return (String) ClassTools.staticFieldValue(nlsClass, sb.toString()); |
| } |
| |
| /** |
| * Retrieves the localized string from the given NLS class by creating the |
| * key. That key is the concatenation of the composite's short class name |
| * with the toString() of the given value separated by an underscore. |
| * |
| * @param nlsClass The NLS class used to retrieve the localized text |
| * @param composite The object used to retrieve the short class name that is |
| * the beginning of the key |
| * @param value The value used to append its toString() to the generated key |
| * @return The localized text associated with the value |
| */ |
| public static final String buildDisplayString(Class<?> nlsClass, |
| Object composite, |
| Object value) { |
| |
| return buildDisplayString(nlsClass, composite.getClass(), value); |
| } |
| |
| /** |
| * Creates the <code>Runnable</code> that will invoke the given |
| * <code>PostExecution</code> in order to its execution to be done in the |
| * UI thread. |
| * |
| * @param dialog The dialog that was just diposed |
| * @param postExecution The post execution once the dialog is disposed |
| * @return The <code>Runnable</code> that will invoke |
| * {@link PostExecution#execute(Dialog)} |
| */ |
| @SuppressWarnings("unchecked") |
| private static <D1 extends Dialog, D2 extends D1> |
| Runnable buildPostExecutionRunnable( |
| final D1 dialog, |
| final PostExecution<D2> postExecution) { |
| |
| return new Runnable() { |
| public void run() { |
| setUserInterfaceActive(false); |
| try { |
| postExecution.execute((D2) dialog); |
| } |
| finally { |
| setUserInterfaceActive(true); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Convenience method for getting the current shell. If the current thread is |
| * not the UI thread, then an invalid thread access exception will be thrown. |
| * |
| * @return The shell, never <code>null</code> |
| */ |
| public static Shell getShell() { |
| |
| // Retrieve the active shell, which can be the shell from any window |
| Shell shell = getStandardDisplay().getActiveShell(); |
| |
| // No shell could be found, revert back to the active workbench window |
| if (shell == null) { |
| shell = getWorkbench().getActiveWorkbenchWindow().getShell(); |
| } |
| |
| // Make sure it's never null |
| if (shell == null) { |
| shell = new Shell(getStandardDisplay().getActiveShell()); |
| } |
| |
| return shell; |
| } |
| |
| /** |
| * Returns the standard display to be used. The method first checks, if the |
| * thread calling this method has an associated display. If so, this display |
| * is returned. Otherwise the method returns the default display. |
| * |
| * @return The current display if not <code>null</code> otherwise the default |
| * display is returned |
| */ |
| public static Display getStandardDisplay() |
| { |
| Display display = Display.getCurrent(); |
| |
| if (display == null) { |
| display = Display.getDefault(); |
| } |
| |
| return display; |
| } |
| |
| public static int getTableHeightHint(Table table, int rows) { |
| if (table.getFont().equals(JFaceResources.getDefaultFont())) |
| table.setFont(JFaceResources.getDialogFont()); |
| int result= table.getItemHeight() * rows + table.getHeaderHeight(); |
| if (table.getLinesVisible()) |
| result+= table.getGridLineWidth() * (rows - 1); |
| return result; |
| } |
| |
| /** |
| * Returns the Platform UI workbench. |
| * |
| * @return The workbench for this plug-in |
| */ |
| public static IWorkbench getWorkbench() { |
| return PlatformUI.getWorkbench(); |
| } |
| |
| /** |
| * Relays out the parents of the <code>Control</code>. This was taken from |
| * the widget <code>Section</code>. |
| * |
| * @param pane The pane to revalidate as well as its parents |
| */ |
| public static void reflow(Composite pane) { |
| |
| for (Composite composite = pane; composite != null; ) { |
| composite.setRedraw(false); |
| composite = composite.getParent(); |
| |
| if (composite instanceof ScrolledForm) { |
| break; |
| } |
| } |
| |
| for (Composite composite = pane; composite != null; ) { |
| composite.layout(true); |
| composite = composite.getParent(); |
| |
| if (composite instanceof ScrolledForm) { |
| ((ScrolledForm) composite).reflow(true); |
| break; |
| } |
| } |
| |
| for (Composite composite = pane; composite != null; ) { |
| composite.setRedraw(true); |
| composite = composite.getParent(); |
| |
| if (composite instanceof ScrolledForm) { |
| break; |
| } |
| } |
| } |
| |
| /** |
| * Sets whether the entire shell and its widgets should be enabled or |
| * everything should be unaccessible. |
| * |
| * @param active <code>true</code> to make all the UI active otherwise |
| * <code>false</code> to deactivate it |
| */ |
| public static void setUserInterfaceActive(boolean active) { |
| Shell[] shells = getStandardDisplay().getShells(); |
| |
| for (Shell shell : shells) { |
| shell.setEnabled(active); |
| } |
| } |
| |
| /** |
| * Asynchronously launches the specified dialog in the UI thread. |
| * |
| * @param dialog The dialog to show on screen |
| * @param postExecution This interface let the caller to invoke a piece of |
| * code once the dialog is disposed |
| */ |
| public static <D1 extends Dialog, D2 extends D1> |
| void show(final D1 dialog, final PostExecution<D2> postExecution) { |
| |
| try { |
| Assert.isNotNull(dialog, "The dialog cannot be null"); |
| Assert.isNotNull(postExecution, "The PostExecution cannot be null"); |
| } |
| catch (AssertionFailedException e) { |
| // Make sure the UI is interactive |
| setUserInterfaceActive(true); |
| throw e; |
| } |
| |
| new Thread() { |
| @Override |
| public void run() { |
| asyncExec( |
| new Runnable() { public void run() { |
| showImp(dialog, postExecution); |
| } |
| } |
| ); |
| }}.start(); |
| } |
| |
| /** |
| * Asynchronously launches the specified dialog in the UI thread. |
| * |
| * @param dialog The dialog to show on screen |
| */ |
| public static void show(Dialog dialog) { |
| show(dialog, NullPostExecution.<Dialog>instance()); |
| } |
| |
| /** |
| * Asynchronously launches the specified dialog in the UI thread. |
| * |
| * @param dialog The dialog to show on screen |
| * @param postExecution This interface let the caller to invoke a piece of |
| * code once the dialog is disposed |
| */ |
| private static <D1 extends Dialog, D2 extends D1> |
| void showImp(D1 dialog, PostExecution<D2> postExecution) { |
| |
| setUserInterfaceActive(true); |
| dialog.open(); |
| |
| if (postExecution != NullPostExecution.<D2>instance()) { |
| asyncExec(buildPostExecutionRunnable(dialog, postExecution)); |
| } |
| } |
| |
| /** |
| * Causes the <code>run()</code> method of the given runnable to be invoked |
| * by the user-interface thread at the next reasonable opportunity. The |
| * thread which calls this method is suspended until the runnable completes. |
| * |
| * @param runnable code to run on the user-interface thread. |
| * @see #asyncExec |
| */ |
| public static void syncExec(Runnable runnable) { |
| getStandardDisplay().syncExec(runnable); |
| } |
| |
| /** |
| * Determines if the current thread is the UI event thread. |
| * |
| * @return <code>true</code> if it's the UI event thread, <code>false</code> |
| * otherwise |
| */ |
| public static boolean uiThread() { |
| return getStandardDisplay().getThread() == Thread.currentThread(); |
| } |
| |
| /** |
| * Determines if the current thread is the UI event thread by using the |
| * thread from which the given viewer's display was instantiated. |
| * |
| * @param viewer The viewer used to determine if the current thread |
| * is the UI event thread |
| * @return <code>true</code> if the current thread is the UI event thread; |
| * <code>false</code> otherwise |
| */ |
| public static boolean uiThread(Viewer viewer) { |
| return uiThread(viewer.getControl()); |
| } |
| |
| /** |
| * Determines if the current thread is the UI event thread by using the |
| * thread from which the given widget's display was instantiated. |
| * |
| * @param widget The widget used to determine if the current thread |
| * is the UI event thread |
| * @return <code>true</code> if the current thread is the UI event thread; |
| * <code>false</code> otherwise |
| */ |
| public static boolean uiThread(Widget widget) { |
| return widget.getDisplay().getThread() == Thread.currentThread(); |
| } |
| |
| /** |
| * This handler is responsible for removing the default value when the combo |
| * has the focus or when the selected item is the default value and to select |
| * it when the combo loses the focus. |
| */ |
| private static class CComboHandler implements ModifyListener, |
| FocusListener { |
| |
| /** |
| * This flag is used to prevent the methods of this handler from |
| * interacting with each other. |
| */ |
| private boolean locked; |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void focusGained(FocusEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| CCombo combo = (CCombo) e.widget; |
| |
| if (combo.getSelectionIndex() == 0) { |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| // The text has to be changed outside of the context of this |
| // listener otherwise the combo won't update because it's currently |
| // notifying its listeners |
| asyncExec(new RemoveDefault(combo, Boolean.FALSE)); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void focusLost(FocusEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| CCombo combo = (CCombo) e.widget; |
| |
| if (combo.getText().length() == 0) { |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| try { |
| combo.setText(combo.getItem(0)); |
| } |
| finally { |
| combo.setData("populating", Boolean.FALSE); |
| locked = false; |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void modifyText(ModifyEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| CCombo combo = (CCombo) e.widget; |
| |
| if (combo.isFocusControl() && |
| combo.getSelectionIndex() <= 0) { |
| |
| // Make sure the current text is the default value |
| String currentValue = combo.getText(); |
| |
| if (currentValue.length() > 0 && |
| combo.getItemCount() > 0 && |
| !currentValue.equals(combo.getItem(0))) { |
| |
| return; |
| } |
| |
| // Remove the default value |
| Object populating = combo.getData("populating"); |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| // The text has to be changed outside of the context of this |
| // listener otherwise the combo won't update because it's currently |
| // notifying its listeners |
| asyncExec(new ModifyText(combo, populating)); |
| } |
| } |
| |
| private class ModifyText implements Runnable { |
| private final CCombo combo; |
| private final Object populating; |
| |
| public ModifyText(CCombo combo, Object populating) { |
| super(); |
| this.combo = combo; |
| this.populating = populating; |
| } |
| |
| public void run() { |
| if (this.combo.isDisposed()) { |
| CComboHandler.this.locked = false; |
| } |
| else { |
| try { |
| String text = this.combo.getText(); |
| |
| if (text.length() == 0) { |
| text = this.combo.getItem(0); |
| this.combo.setText(text); |
| } |
| |
| this.combo.setSelection(new Point(0, text.length())); |
| } |
| finally { |
| this.combo.setData("populating", this.populating); |
| CComboHandler.this.locked = false; |
| } |
| } |
| } |
| } |
| |
| private class RemoveDefault implements Runnable { |
| private final CCombo combo; |
| private final Object populating; |
| |
| public RemoveDefault(CCombo combo, Object populating) { |
| super(); |
| this.combo = combo; |
| this.populating = populating; |
| } |
| |
| public void run() { |
| if (this.combo.isDisposed()) { |
| CComboHandler.this.locked = false; |
| } |
| else { |
| try { |
| this.combo.setText(""); |
| } |
| finally { |
| this.combo.setData("populating", this.populating); |
| CComboHandler.this.locked = false; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * This handler is responsible for removing the default value when the combo |
| * has the focus or when the selected item is the default value and to select |
| * it when the combo loses the focus. |
| */ |
| private static class ComboHandler implements ModifyListener, |
| FocusListener { |
| |
| /** |
| * This flag is used to prevent the methods of this handler from |
| * interacting with each other. |
| */ |
| private boolean locked; |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void focusGained(FocusEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| Combo combo = (Combo) e.widget; |
| |
| if (combo.getSelectionIndex() == 0) { |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| // The text has to be changed outside of the context of this |
| // listener otherwise the combo won't update because it's currently |
| // notifying its listeners |
| asyncExec(new RemoveDefault(combo, Boolean.FALSE)); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void focusLost(FocusEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| Combo combo = (Combo) e.widget; |
| |
| if (combo.getText().length() == 0) { |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| try { |
| combo.select(0); |
| } |
| finally { |
| combo.setData("populating", Boolean.FALSE); |
| locked = false; |
| } |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| */ |
| public void modifyText(ModifyEvent e) { |
| |
| if (locked) { |
| return; |
| } |
| |
| Combo combo = (Combo) e.widget; |
| |
| if (combo.isFocusControl() && |
| combo.getSelectionIndex() == 0) { |
| |
| Object populating = combo.getData("populating"); |
| combo.setData("populating", Boolean.TRUE); |
| locked = true; |
| |
| // The text has to be changed outside of the context of this |
| // listener otherwise the combo won't update because it's currently |
| // notifying its listeners |
| asyncExec(new ModifyText(combo, populating)); |
| } |
| } |
| |
| private class ModifyText implements Runnable { |
| private final Combo combo; |
| private final Object populating; |
| |
| public ModifyText(Combo combo, Object populating) { |
| super(); |
| this.combo = combo; |
| this.populating = populating; |
| } |
| |
| public void run() { |
| if (this.combo.isDisposed()) { |
| ComboHandler.this.locked = false; |
| } |
| else { |
| try { |
| String text = this.combo.getText(); |
| |
| if (text.length() == 0) { |
| text = this.combo.getItem(0); |
| this.combo.setText(text); |
| } |
| |
| this.combo.setSelection(new Point(0, text.length())); |
| } |
| finally { |
| this.combo.setData("populating", this.populating); |
| ComboHandler.this.locked = false; |
| } |
| } |
| } |
| } |
| |
| private class RemoveDefault implements Runnable { |
| private final Combo combo; |
| private final Object populating; |
| |
| public RemoveDefault(Combo combo, Object populating) { |
| super(); |
| this.combo = combo; |
| this.populating = populating; |
| } |
| |
| public void run() { |
| if (this.combo.isDisposed()) { |
| ComboHandler.this.locked = false; |
| } |
| else { |
| try { |
| this.combo.setText(""); |
| } |
| finally { |
| this.combo.setData("populating", this.populating); |
| ComboHandler.this.locked = false; |
| } |
| } |
| } |
| } |
| } |
| } |