001: /*
002: * uDig - User Friendly Desktop Internet GIS client http://udig.refractions.net (C) 2004,
003: * Refractions Research Inc. This library is free software; you can redistribute it and/or modify it
004: * under the terms of the GNU Lesser General Public License as published by the Free Software
005: * Foundation; version 2.1 of the License. This library is distributed in the hope that it will be
006: * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
007: * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
008: */
009: package net.refractions.udig.ui.operations;
010:
011: import java.lang.reflect.Array;
012: import java.util.Iterator;
013: import java.util.LinkedList;
014: import java.util.List;
015: import java.util.concurrent.Executor;
016: import java.util.concurrent.Executors;
017:
018: import net.refractions.udig.core.AdapterUtil;
019: import net.refractions.udig.internal.ui.Images;
020: import net.refractions.udig.internal.ui.UiPlugin;
021: import net.refractions.udig.internal.ui.operations.OperationCategory;
022: import net.refractions.udig.internal.ui.operations.OperationMenuFactory;
023: import net.refractions.udig.ui.internal.Messages;
024: import net.refractions.udig.ui.operations.EnablementUtil.EnablesForData;
025:
026: import org.eclipse.core.runtime.CoreException;
027: import org.eclipse.core.runtime.IConfigurationElement;
028: import org.eclipse.core.runtime.IProgressMonitor;
029: import org.eclipse.core.runtime.IStatus;
030: import org.eclipse.core.runtime.NullProgressMonitor;
031: import org.eclipse.core.runtime.Status;
032: import org.eclipse.core.runtime.jobs.Job;
033: import org.eclipse.jface.action.Action;
034: import org.eclipse.jface.dialogs.MessageDialog;
035: import org.eclipse.jface.viewers.ISelection;
036: import org.eclipse.jface.viewers.IStructuredSelection;
037: import org.eclipse.swt.widgets.Display;
038: import org.eclipse.swt.widgets.Event;
039: import org.eclipse.ui.ISelectionListener;
040: import org.eclipse.ui.IWorkbenchPart;
041: import org.eclipse.ui.PlatformUI;
042:
043: /**
044: * Creates an Action that runs an operation in a background thread when triggered.
045: *
046: * @author jeichar
047: * @since 0.3
048: */
049: public class OpAction extends Action implements ISelectionListener {
050: IConfigurationElement configElem;
051: volatile IStructuredSelection selection;
052: private IOp operation;
053: private RunJob runJob;
054: private String targetClass;
055: OperationCategory category;
056: String menuPath;
057: private OpFilter filter;
058: private EnablesForData enablesForData;
059: private volatile NullProgressMonitor monitor = new NullProgressMonitor();
060: private boolean loadingError;
061: private final static Executor executor = Executors
062: .newFixedThreadPool(1);
063:
064: /**
065: * Subclasses must have the same constructor signature as this constructor,
066: * {@linkplain OperationMenuFactory} creates OpActions using this
067: * constructor.
068: *
069: * Construct <code>OpAction</code>.
070: *
071: */
072: public OpAction(IConfigurationElement element) {
073: this .configElem = element;
074: setId(element.getAttribute("id")); //$NON-NLS-1$
075: setText(element.getAttribute("name")); //$NON-NLS-1$
076: String icon = element.getAttribute("icon"); //$NON-NLS-1$
077: if (icon != null)
078: setImageDescriptor(Images.getDescriptor(icon));
079: String tooltip = element.getAttribute("tooltip"); //$NON-NLS-1$
080: if (tooltip != null)
081: setToolTipText(tooltip);
082: this .targetClass = element.getAttribute("targetClass"); //$NON-NLS-1$
083: this .menuPath = element.getAttribute("menuPath"); //$NON-NLS-1$
084: this .runJob = new RunJob(getText());
085: enablesForData = EnablementUtil.parseEnablesFor(element
086: .getAttribute("enablesFor"), configElem);//$NON-NLS-1$
087: filter = EnablementUtil
088: .parseEnablement(
089: element.getNamespaceIdentifier()
090: + "." + element.getName(), element.getChildren("enablement")); //$NON-NLS-1$ //$NON-NLS-2$
091: }
092:
093: public void run() {
094: runJob.display = Display.getCurrent();
095: runJob.schedule();
096: }
097:
098: public void runWithEvent(Event event) {
099: runJob.display = event.display;
100: runJob.schedule();
101: }
102:
103: IOp getOperation() {
104: if (operation == null) {
105: try {
106: operation = (IOp) configElem
107: .createExecutableExtension("class"); //$NON-NLS-1$
108: } catch (CoreException e) {
109: final Display display = Display.getDefault();
110: Runnable runnable = new Runnable() {
111: public void run() {
112: setEnabled(false);
113: MessageDialog.openError(display
114: .getActiveShell(),
115: Messages.OpAction_errorTitle,
116: Messages.OpAction_errorMessage);
117: }
118: };
119: loadingError = true;
120: display.asyncExec(runnable);
121: }
122: }
123:
124: return operation;
125: }
126:
127: class RunJob extends Job {
128: Display display;
129:
130: /**
131: * Construct <code>RunJob</code>.
132: *
133: * @param name
134: */
135: public RunJob(String name) {
136: super (name);
137: // setUser(true);
138: }
139:
140: @Override
141: public boolean belongsTo(Object family) {
142: return family == OpAction.class;
143: }
144:
145: protected IStatus run(IProgressMonitor monitor) {
146: try {
147: Object target;
148: if (enablesForData.minHits == 1
149: && enablesForData.exactMatch) {
150: try {
151: target = AdapterUtil.instance
152: .getOperationTarget(targetClass,
153: selection.getFirstElement(),
154: monitor);
155: if (target == null) {
156: UiPlugin
157: .log(
158: "Factory adapting " + selection.getFirstElement().getClass().getName() + " to a " + //$NON-NLS-1$ //$NON-NLS-2$
159: targetClass
160: + " is returning null even though it is advertising that it can " + //$NON-NLS-1$
161: "do the adaptation",
162: null); //$NON-NLS-1$
163: return Status.OK_STATUS;
164: }
165: } catch (Throwable e) {
166: UiPlugin.log(null, e);
167: return Status.OK_STATUS;
168: }
169: } else {
170: List<Object> targets = new LinkedList<Object>();
171: for (Iterator iter = selection.iterator(); iter
172: .hasNext();) {
173: @SuppressWarnings("unchecked")
174: Object entry = iter.next();
175: try {
176: Object operationTarget = AdapterUtil.instance
177: .getOperationTarget(targetClass,
178: entry, monitor);
179: if (operationTarget == null) {
180: UiPlugin
181: .log(
182: "Factory adapting " + entry.getClass().getName() + " to a " + //$NON-NLS-1$ //$NON-NLS-2$
183: targetClass
184: + " is returning null even though it is advertising that it can " + //$NON-NLS-1$
185: "do the adaptation",
186: null); //$NON-NLS-1$
187: return Status.OK_STATUS;
188: } else {
189: targets.add(operationTarget);
190: }
191: } catch (Throwable e) {
192: UiPlugin.log(null, e);
193: return Status.OK_STATUS;
194: }
195: }
196: Class targetClass = targets.get(0).getClass();
197: Object[] tmp = (Object[]) Array.newInstance(
198: targetClass, targets.size());
199: if (enablesForData.minHits == 1
200: && enablesForData.exactMatch) {
201: target = targets.size() > 0 ? targets.get(0)
202: : null;
203: } else {
204: target = targets.toArray(tmp);
205: }
206:
207: }
208: IOp op = getOperation();
209: op.op(display, target, monitor);
210: } catch (Throwable e) {
211: UiPlugin.log(null, e);
212: }
213: return Status.OK_STATUS;
214: }
215:
216: }
217:
218: /**
219: * Determines whether the current operation can operate on the object.
220: * @return true if object can be used as the operation input.
221: */
222: public boolean isValid(Object obj) {
223: return !this .loadingError
224: && AdapterUtil.instance.canAdaptTo(targetClass, obj)
225: && filter.accept(obj);
226: }
227:
228: /**
229: * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection)
230: */
231: public void selectionChanged(IWorkbenchPart part,
232: ISelection selection) {
233: if (selection instanceof IStructuredSelection) {
234: synchronized (this ) {
235: IStructuredSelection structured = (IStructuredSelection) selection;
236:
237: updateEnablement(structured, false);
238:
239: }
240: }
241: }
242:
243: /**
244: * Updates the enablement status of this action.
245: *
246: * @param structured the selection to use to determine the enablement.
247: * @param executeSynchronous
248: */
249: public void updateEnablement(IStructuredSelection structured,
250: boolean executeSynchronous) {
251: if (structured.equals(this .selection)
252: || PlatformUI.getWorkbench().isClosing())
253: return;
254:
255: NullProgressMonitor lastMonitor = monitor;
256: monitor = new NullProgressMonitor();
257: lastMonitor.setCanceled(true);
258:
259: SetEnablement enablement = new SetEnablement(structured,
260: monitor, executeSynchronous);
261: if (!executeSynchronous) {
262: setEnabled(false);
263: executor.execute(enablement);
264: } else {
265: enablement.run();
266: }
267: }
268:
269: public void setCategory(OperationCategory category) {
270: this .category = category;
271: }
272:
273: public String getMenuPath() {
274: return menuPath;
275: }
276:
277: @Override
278: public String toString() {
279: return getMenuPath() + "/" + getText(); //$NON-NLS-1$
280: }
281:
282: private class SetEnablement implements Runnable {
283:
284: private IStructuredSelection structured;
285: private IProgressMonitor monitor;
286: private boolean execSync;
287:
288: public SetEnablement(IStructuredSelection structured,
289: IProgressMonitor monitor, boolean executeSynchronous) {
290: this .structured = structured;
291: this .monitor = monitor;
292: this .execSync = executeSynchronous;
293: }
294:
295: public void run() {
296: if (PlatformUI.getWorkbench().isClosing())
297: return;
298: boolean enabled = false;
299: int hits = 0;
300:
301: for (Iterator iter = structured.iterator(); iter.hasNext();) {
302: if (monitor.isCanceled())
303: return;
304: @SuppressWarnings("unchecked")
305: Object obj = iter.next();
306: if (isValid(obj)) {
307: hits++;
308: } else {
309: hits = -1;
310: break;
311: }
312: }
313:
314: if (monitor.isCanceled())
315: return;
316:
317: if (hits >= enablesForData.minHits) {
318: if (enablesForData.exactMatch
319: && hits == enablesForData.minHits)
320: enabled = true;
321: else if (!enablesForData.exactMatch
322: && hits >= enablesForData.minHits)
323: enabled = true;
324: }
325:
326: final boolean finalEnabled = enabled;
327: final boolean oldEnabledState = isEnabled();
328: Runnable runnable = new Runnable() {
329: public void run() {
330: if (structured.equals(OpAction.this.selection))
331: return;
332: OpAction.this.selection = structured;
333:
334: setEnabled(finalEnabled);
335:
336: if (category != null
337: && oldEnabledState != finalEnabled)
338: category.enablementChanged();
339: }
340: };
341: if (execSync) {
342: if (Display.getCurrent() != null) {
343: runnable.run();
344: } else {
345: Display.getDefault().syncExec(runnable);
346: }
347: } else {
348: Display.getDefault().asyncExec(runnable);
349: }
350: }
351: }
352: }
|