Mercurial > projects > dwt2
comparison org.eclipse.core.commands/src/org/eclipse/core/commands/operations/DefaultOperationHistory.d @ 12:bc29606a740c
Added dwt-addons in original directory structure of eclipse.org
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 14 Mar 2009 18:23:29 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
11:43904fec5dca | 12:bc29606a740c |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2005, 2007 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module org.eclipse.core.commands.operations.DefaultOperationHistory; | |
14 | |
15 import org.eclipse.core.commands.ExecutionException; | |
16 import org.eclipse.core.commands.util.Tracing; | |
17 import org.eclipse.core.runtime.Assert; | |
18 import org.eclipse.core.runtime.IAdaptable; | |
19 import org.eclipse.core.runtime.IProgressMonitor; | |
20 import org.eclipse.core.runtime.ISafeRunnable; | |
21 import org.eclipse.core.runtime.IStatus; | |
22 import org.eclipse.core.runtime.ListenerList; | |
23 import org.eclipse.core.runtime.OperationCanceledException; | |
24 import org.eclipse.core.runtime.SafeRunner; | |
25 import org.eclipse.core.runtime.Status; | |
26 | |
27 import org.eclipse.core.commands.operations.IOperationHistory; | |
28 import org.eclipse.core.commands.operations.IUndoContext; | |
29 import org.eclipse.core.commands.operations.IUndoableOperation; | |
30 import org.eclipse.core.commands.operations.ICompositeOperation; | |
31 import org.eclipse.core.commands.operations.IOperationApprover; | |
32 import org.eclipse.core.commands.operations.IOperationApprover2; | |
33 import org.eclipse.core.commands.operations.IOperationHistoryListener; | |
34 import org.eclipse.core.commands.operations.OperationHistoryEvent; | |
35 import org.eclipse.core.commands.operations.IAdvancedUndoableOperation; | |
36 | |
37 import java.lang.all; | |
38 import java.util.Map; | |
39 import java.util.HashMap; | |
40 import java.util.List; | |
41 import java.util.ArrayList; | |
42 import java.util.Collections; | |
43 import java.util.Iterator; | |
44 | |
45 /** | |
46 * <p> | |
47 * A base implementation of IOperationHistory that implements a linear undo and | |
48 * redo model . The most recently added operation is available for undo, and the | |
49 * most recently undone operation is available for redo. | |
50 * </p> | |
51 * <p> | |
52 * If the operation eligible for undo is not in a state where it can be undone, | |
53 * then no undo is available. No other operations are considered. Likewise, if | |
54 * the operation available for redo cannot be redone, then no redo is available. | |
55 * </p> | |
56 * <p> | |
57 * Implementations for the direct undo and redo of a specified operation are | |
58 * available. If a strict linear undo is to be enforced, than an | |
59 * IOperationApprover should be installed that prevents undo and redo of any | |
60 * operation that is not the most recently undone or redone operation in all of | |
61 * its undo contexts. | |
62 * </p> | |
63 * <p> | |
64 * The data structures used by the DefaultOperationHistory are synchronized, and | |
65 * entry points that modify the undo and redo history concurrently are also | |
66 * synchronized. This means that the DefaultOperationHistory is relatively | |
67 * "thread-friendly" in its implementation. Outbound notifications or operation | |
68 * approval requests will occur on the thread that initiated the request. | |
69 * Clients may use DefaultOperationHistory API from any thread; however, | |
70 * listeners or operation approvers that receive notifications from the | |
71 * DefaultOperationHistory must be prepared to receive these notifications from | |
72 * a background thread. Any UI access occurring inside these notifications must | |
73 * be properly synchronized using the techniques specified by the client's | |
74 * widget library. | |
75 * </p> | |
76 * | |
77 * <p> | |
78 * This implementation is not intended to be subclassed. | |
79 * </p> | |
80 * | |
81 * @see org.eclipse.core.commands.operations.IOperationHistory | |
82 * @see org.eclipse.core.commands.operations.IOperationApprover | |
83 * | |
84 * @since 3.1 | |
85 */ | |
86 public final class DefaultOperationHistory : IOperationHistory { | |
87 /** | |
88 * This flag can be set to <code>true</code> if the history should print | |
89 * information to <code>System.out</code> whenever notifications about | |
90 * changes to the history occur. This flag should be used for debug purposes | |
91 * only. | |
92 */ | |
93 public static bool DEBUG_OPERATION_HISTORY_NOTIFICATION = false; | |
94 | |
95 /** | |
96 * This flag can be set to <code>true</code> if the history should print | |
97 * information to <code>System.out</code> whenever an unexpected condition | |
98 * arises. This flag should be used for debug purposes only. | |
99 */ | |
100 public static bool DEBUG_OPERATION_HISTORY_UNEXPECTED = false; | |
101 | |
102 /** | |
103 * This flag can be set to <code>true</code> if the history should print | |
104 * information to <code>System.out</code> whenever an undo context is | |
105 * disposed. This flag should be used for debug purposes only. | |
106 */ | |
107 public static bool DEBUG_OPERATION_HISTORY_DISPOSE = false; | |
108 | |
109 /** | |
110 * This flag can be set to <code>true</code> if the history should print | |
111 * information to <code>System.out</code> during the open/close sequence. | |
112 * This flag should be used for debug purposes only. | |
113 */ | |
114 public static bool DEBUG_OPERATION_HISTORY_OPENOPERATION = false; | |
115 | |
116 /** | |
117 * This flag can be set to <code>true</code> if the history should print | |
118 * information to <code>System.out</code> whenever an operation is not | |
119 * approved. This flag should be used for debug purposes only. | |
120 */ | |
121 public static bool DEBUG_OPERATION_HISTORY_APPROVAL = false; | |
122 | |
123 static const int DEFAULT_LIMIT = 20; | |
124 | |
125 /** | |
126 * the list of {@link IOperationApprover}s | |
127 */ | |
128 ListenerList approvers; | |
129 | |
130 /** | |
131 * a map of undo limits per context | |
132 */ | |
133 private Map limits; | |
134 | |
135 /** | |
136 * the list of {@link IOperationHistoryListener}s | |
137 */ | |
138 ListenerList listeners; | |
139 | |
140 /** | |
141 * the list of operations available for redo, LIFO | |
142 */ | |
143 private List redoList; | |
144 | |
145 /** | |
146 * the list of operations available for undo, LIFO | |
147 */ | |
148 private List undoList; | |
149 | |
150 /** | |
151 * a lock that is used to synchronize access between the undo and redo | |
152 * history | |
153 */ | |
154 final Object undoRedoHistoryLock; | |
155 | |
156 /** | |
157 * An operation that is "absorbing" all other operations while it is open. | |
158 * When this is not null, other operations added or executed are added to | |
159 * this composite. | |
160 * | |
161 */ | |
162 private ICompositeOperation openComposite = null; | |
163 | |
164 /** | |
165 * a lock that is used to synchronize access to the open composite. | |
166 */ | |
167 const Object openCompositeLock; | |
168 | |
169 /** | |
170 * Create an instance of DefaultOperationHistory. | |
171 */ | |
172 public this() { | |
173 openCompositeLock = new Object(); | |
174 undoRedoHistoryLock = new Object(); | |
175 approvers = new ListenerList(ListenerList.IDENTITY); | |
176 limits = Collections.synchronizedMap(new HashMap()); | |
177 listeners = new ListenerList(ListenerList.IDENTITY); | |
178 redoList = Collections.synchronizedList(new ArrayList()); | |
179 undoList = Collections.synchronizedList(new ArrayList()); | |
180 } | |
181 | |
182 /* | |
183 * (non-Javadoc) | |
184 * | |
185 * @see org.eclipse.core.commands.operations.IOperationHistory#add(org.eclipse.core.commands.operations.IUndoableOperation) | |
186 */ | |
187 public void add(IUndoableOperation operation) { | |
188 Assert.isNotNull( cast(Object)operation); | |
189 | |
190 /* | |
191 * If we are in the middle of executing an open batching operation, and | |
192 * this is not that operation, then we need only add the context of the | |
193 * new operation to the batch. The operation itself is disposed since we | |
194 * will never undo or redo it. We consider it to be triggered by the | |
195 * batching operation and assume that its undo will be triggered by the | |
196 * batching operation undo. | |
197 */ | |
198 synchronized (openCompositeLock) { | |
199 if (openComposite !is null && openComposite !is operation) { | |
200 openComposite.add(operation); | |
201 return; | |
202 } | |
203 } | |
204 | |
205 if (checkUndoLimit(operation)) { | |
206 synchronized (undoRedoHistoryLock) { | |
207 undoList.add(cast(Object)operation); | |
208 } | |
209 notifyAdd(operation); | |
210 | |
211 // flush redo stack for related contexts | |
212 IUndoContext[] contexts = operation.getContexts(); | |
213 for (int i = 0; i < contexts.length; i++) { | |
214 flushRedo(contexts[i]); | |
215 } | |
216 } else { | |
217 // Dispose the operation since we will not have a reference to it. | |
218 operation.dispose(); | |
219 } | |
220 } | |
221 | |
222 /** | |
223 * <p> | |
224 * Add the specified approver to the list of operation approvers consulted | |
225 * by the operation history before an undo or redo is allowed to proceed. | |
226 * This method has no effect if the instance being added is already in the | |
227 * list. | |
228 * </p> | |
229 * <p> | |
230 * Operation approvers must be prepared to receive these the operation | |
231 * approval messages from a background thread. Any UI access occurring | |
232 * inside the implementation must be properly synchronized using the | |
233 * techniques specified by the client's widget library. | |
234 * </p> | |
235 * | |
236 * @param approver | |
237 * the IOperationApprover to be added as an approver. | |
238 * | |
239 */ | |
240 | |
241 public void addOperationApprover(IOperationApprover approver) { | |
242 approvers.add(cast(Object)approver); | |
243 } | |
244 | |
245 /** | |
246 * <p> | |
247 * Add the specified listener to the list of operation history listeners | |
248 * that are notified about changes in the history or operations that are | |
249 * executed, undone, or redone. This method has no effect if the instance | |
250 * being added is already in the list. | |
251 * </p> | |
252 * <p> | |
253 * Operation history listeners must be prepared to receive notifications | |
254 * from a background thread. Any UI access occurring inside the | |
255 * implementation must be properly synchronized using the techniques | |
256 * specified by the client's widget library. | |
257 * </p> | |
258 * | |
259 * @param listener | |
260 * the IOperationHistoryListener to be added as a listener. | |
261 * | |
262 * @see org.eclipse.core.commands.operations.IOperationHistoryListener | |
263 * @see org.eclipse.core.commands.operations.OperationHistoryEvent | |
264 */ | |
265 public void addOperationHistoryListener(IOperationHistoryListener listener) { | |
266 listeners.add(cast(Object)listener); | |
267 } | |
268 | |
269 /* | |
270 * (non-Javadoc) | |
271 * | |
272 * @see org.eclipse.core.commands.operations.IOperationHistory#canRedo(org.eclipse.core.commands.operations.IUndoContext) | |
273 */ | |
274 public bool canRedo(IUndoContext context) { | |
275 // null context is allowed and passed through | |
276 IUndoableOperation operation = getRedoOperation(context); | |
277 return (operation !is null && operation.canRedo()); | |
278 } | |
279 | |
280 /* | |
281 * (non-Javadoc) | |
282 * | |
283 * @see org.eclipse.core.commands.operations.IOperationHistory#canUndo(org.eclipse.core.commands.operations.IUndoContext) | |
284 */ | |
285 public bool canUndo(IUndoContext context) { | |
286 // null context is allowed and passed through | |
287 IUndoableOperation operation = getUndoOperation(context); | |
288 return (operation !is null && operation.canUndo()); | |
289 } | |
290 | |
291 /** | |
292 * Check the redo limit before adding an operation. In theory the redo limit | |
293 * should never be reached, because the redo items are transferred from the | |
294 * undo history, which has the same limit. The redo history is cleared | |
295 * whenever a new operation is added. We check for completeness since | |
296 * implementations may change over time. | |
297 * | |
298 * Return a bool indicating whether the redo should proceed. | |
299 */ | |
300 private bool checkRedoLimit(IUndoableOperation operation) { | |
301 IUndoContext[] contexts = operation.getContexts(); | |
302 for (int i = 0; i < contexts.length; i++) { | |
303 int limit = getLimit(contexts[i]); | |
304 if (limit > 0) { | |
305 forceRedoLimit(contexts[i], limit - 1); | |
306 } else { | |
307 // this context has a 0 limit | |
308 operation.removeContext(contexts[i]); | |
309 } | |
310 } | |
311 return operation.getContexts().length > 0; | |
312 } | |
313 | |
314 /** | |
315 * Check the undo limit before adding an operation. Return a bool | |
316 * indicating whether the undo should proceed. | |
317 */ | |
318 private bool checkUndoLimit(IUndoableOperation operation) { | |
319 IUndoContext[] contexts = operation.getContexts(); | |
320 for (int i = 0; i < contexts.length; i++) { | |
321 int limit = getLimit(contexts[i]); | |
322 if (limit > 0) { | |
323 forceUndoLimit(contexts[i], limit - 1); | |
324 } else { | |
325 // this context has a 0 limit | |
326 operation.removeContext(contexts[i]); | |
327 } | |
328 } | |
329 return operation.getContexts().length > 0; | |
330 } | |
331 | |
332 /* | |
333 * (non-Javadoc) | |
334 * | |
335 * @see org.eclipse.core.commands.operations.IOperationHistory#dispose(org.eclipse.core.commands.operations.IUndoContext, | |
336 * bool, bool, bool) | |
337 */ | |
338 public void dispose(IUndoContext context, bool flushUndo_, | |
339 bool flushRedo_, bool flushContext_) { | |
340 // dispose of any limit that was set for the context if it is not to be | |
341 // used again. | |
342 if (flushContext_) { | |
343 if (DEBUG_OPERATION_HISTORY_DISPOSE) { | |
344 Tracing.printTrace("OPERATIONHISTORY", "Flushing context " //$NON-NLS-1$//$NON-NLS-2$ | |
345 ~ (cast(Object)context).toString ); | |
346 } | |
347 flushUndo(context); | |
348 flushRedo(context); | |
349 limits.remove(cast(Object)context); | |
350 return; | |
351 } | |
352 if (flushUndo_) { | |
353 flushUndo(context); | |
354 } | |
355 if (flushRedo_) { | |
356 flushRedo(context); | |
357 } | |
358 | |
359 } | |
360 | |
361 /** | |
362 * Perform the redo. All validity checks have already occurred. | |
363 * | |
364 * @param monitor | |
365 * @param operation | |
366 */ | |
367 private IStatus doRedo(IProgressMonitor monitor, IAdaptable info, | |
368 IUndoableOperation operation) { | |
369 | |
370 IStatus status = getRedoApproval(operation, info); | |
371 if (status.isOK()) { | |
372 notifyAboutToRedo(operation); | |
373 try { | |
374 status = operation.redo(monitor, info); | |
375 } catch (OperationCanceledException e) { | |
376 status = Status.CANCEL_STATUS; | |
377 } catch (ExecutionException e) { | |
378 notifyNotOK(operation); | |
379 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
380 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
381 "ExecutionException while redoing " ~ (cast(Object)operation).toString ); //$NON-NLS-1$ | |
382 } | |
383 throw e; | |
384 } catch (Exception e) { | |
385 notifyNotOK(operation); | |
386 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
387 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
388 "Exception while redoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
389 } | |
390 throw new ExecutionException( | |
391 "While redoing the operation, an exception occurred", e); //$NON-NLS-1$ | |
392 } | |
393 } | |
394 | |
395 // if successful, the operation is removed from the redo history and | |
396 // placed back in the undo history. | |
397 if (status.isOK()) { | |
398 bool addedToUndo = true; | |
399 synchronized (undoRedoHistoryLock) { | |
400 redoList.remove(cast(Object)operation); | |
401 if (checkUndoLimit(operation)) { | |
402 undoList.add(cast(Object)operation); | |
403 } else { | |
404 addedToUndo = false; | |
405 } | |
406 } | |
407 // dispose the operation since we could not add it to the | |
408 // stack and will no longer have a reference to it. | |
409 if (!addedToUndo) { | |
410 operation.dispose(); | |
411 } | |
412 | |
413 // notify listeners must happen after history is updated | |
414 notifyRedone(operation); | |
415 } else { | |
416 notifyNotOK(operation, status); | |
417 } | |
418 | |
419 return status; | |
420 } | |
421 | |
422 /** | |
423 * Perform the undo. All validity checks have already occurred. | |
424 * | |
425 * @param monitor | |
426 * @param operation | |
427 */ | |
428 private IStatus doUndo(IProgressMonitor monitor, IAdaptable info, | |
429 IUndoableOperation operation) { | |
430 IStatus status = getUndoApproval(operation, info); | |
431 if (status.isOK()) { | |
432 notifyAboutToUndo(operation); | |
433 try { | |
434 status = operation.undo(monitor, info); | |
435 } catch (OperationCanceledException e) { | |
436 status = Status.CANCEL_STATUS; | |
437 } catch (ExecutionException e) { | |
438 notifyNotOK(operation); | |
439 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
440 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
441 "ExecutionException while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
442 } | |
443 throw e; | |
444 } catch (Exception e) { | |
445 notifyNotOK(operation); | |
446 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
447 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
448 "Exception while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
449 } | |
450 throw new ExecutionException( | |
451 "While undoing the operation, an exception occurred", e); //$NON-NLS-1$ | |
452 } | |
453 } | |
454 // if successful, the operation is removed from the undo history and | |
455 // placed in the redo history. | |
456 if (status.isOK()) { | |
457 bool addedToRedo = true; | |
458 synchronized (undoRedoHistoryLock) { | |
459 undoList.remove(cast(Object)operation); | |
460 if (checkRedoLimit(operation)) { | |
461 redoList.add(cast(Object)operation); | |
462 } else { | |
463 addedToRedo = false; | |
464 } | |
465 } | |
466 // dispose the operation since we could not add it to the | |
467 // stack and will no longer have a reference to it. | |
468 if (!addedToRedo) { | |
469 operation.dispose(); | |
470 } | |
471 // notification occurs after the undo and redo histories are | |
472 // adjusted | |
473 notifyUndone(operation); | |
474 } else { | |
475 notifyNotOK(operation, status); | |
476 } | |
477 return status; | |
478 } | |
479 | |
480 /* | |
481 * (non-Javadoc) | |
482 * | |
483 * @see org.eclipse.core.commands.operations.IOperationHistory#execute(org.eclipse.core.commands.operations.IUndoableOperation, | |
484 * org.eclipse.core.runtime.IProgressMonitor, | |
485 * org.eclipse.core.runtime.IAdaptable) | |
486 */ | |
487 public IStatus execute(IUndoableOperation operation, | |
488 IProgressMonitor monitor, IAdaptable info) { | |
489 Assert.isNotNull(cast(Object)operation); | |
490 | |
491 // error if operation is invalid | |
492 if (!operation.canExecute()) { | |
493 return IOperationHistory.OPERATION_INVALID_STATUS; | |
494 } | |
495 | |
496 // check with the operation approvers | |
497 IStatus status = getExecuteApproval(operation, info); | |
498 if (!status.isOK()) { | |
499 // not approved. No notifications are sent, just return the status. | |
500 return status; | |
501 } | |
502 | |
503 /* | |
504 * If we are in the middle of an open composite, then we will add this | |
505 * operation to the open operation rather than add the operation to the | |
506 * history. We will still execute it. | |
507 */ | |
508 bool merging = false; | |
509 synchronized (openCompositeLock) { | |
510 if (openComposite !is null) { | |
511 // the composite shouldn't be executed explicitly while it is | |
512 // still | |
513 // open | |
514 if (openComposite is operation) { | |
515 return IOperationHistory.OPERATION_INVALID_STATUS; | |
516 } | |
517 openComposite.add(operation); | |
518 merging = true; | |
519 } | |
520 } | |
521 | |
522 /* | |
523 * Execute the operation | |
524 */ | |
525 if (!merging) { | |
526 notifyAboutToExecute(operation); | |
527 } | |
528 try { | |
529 status = operation.execute(monitor, info); | |
530 } catch (OperationCanceledException e) { | |
531 status = Status.CANCEL_STATUS; | |
532 } catch (ExecutionException e) { | |
533 notifyNotOK(operation); | |
534 throw e; | |
535 } catch (Exception e) { | |
536 notifyNotOK(operation); | |
537 throw new ExecutionException( | |
538 "While executing the operation, an exception occurred", e); //$NON-NLS-1$ | |
539 } | |
540 | |
541 // if successful, the notify listeners are notified and the operation is | |
542 // added to the history | |
543 if (!merging) { | |
544 if (status.isOK()) { | |
545 notifyDone(operation); | |
546 add(operation); | |
547 } else { | |
548 notifyNotOK(operation, status); | |
549 // dispose the operation since we did not add it to the stack | |
550 // and will no longer have a reference to it. | |
551 operation.dispose(); | |
552 } | |
553 } | |
554 // all other severities are not interpreted. Simply return the status. | |
555 return status; | |
556 } | |
557 | |
558 /* | |
559 * Filter the specified list to include only the specified undo context. | |
560 */ | |
561 private IUndoableOperation[] filter(List list, IUndoContext context) { | |
562 /* | |
563 * This method is used whenever there is a need to filter the undo or | |
564 * redo history on a particular context. Currently there are no caches | |
565 * kept to optimize repeated requests for the same filter. If benchmarks | |
566 * show this to be a common pattern that causes performances problems, | |
567 * we could implement a filtered cache here that is nullified whenever | |
568 * the global history changes. | |
569 */ | |
570 | |
571 List filtered = new ArrayList(); | |
572 Iterator iterator = list.iterator(); | |
573 synchronized (undoRedoHistoryLock) { | |
574 while (iterator.hasNext()) { | |
575 IUndoableOperation operation = cast(IUndoableOperation) iterator | |
576 .next(); | |
577 if (operation.hasContext(context)) { | |
578 filtered.add(cast(Object)operation); | |
579 } | |
580 } | |
581 } | |
582 return arraycast!(IUndoableOperation)( filtered | |
583 .toArray()); | |
584 } | |
585 | |
586 /* | |
587 * Flush the redo stack of all operations that have the given context. | |
588 */ | |
589 private void flushRedo(IUndoContext context) { | |
590 if (DEBUG_OPERATION_HISTORY_DISPOSE) { | |
591 Tracing.printTrace("OPERATIONHISTORY", "Flushing redo history for " //$NON-NLS-1$ //$NON-NLS-2$ | |
592 ~ (cast(Object)context).toString ); | |
593 } | |
594 | |
595 IUndoableOperation[] filtered = filter(redoList, context); | |
596 for (int i = 0; i < filtered.length; i++) { | |
597 IUndoableOperation operation = cast(IUndoableOperation) filtered[i]; | |
598 if (context is GLOBAL_UNDO_CONTEXT | |
599 || operation.getContexts().length is 1) { | |
600 // remove the operation if it only has the context or we are | |
601 // flushing all | |
602 redoList.remove(cast(Object)operation); | |
603 internalRemove(operation); | |
604 } else { | |
605 // remove the reference to the context. | |
606 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786 | |
607 // It is not enough to simply remove the context. There could | |
608 // be one or more contexts that match the one we are trying to | |
609 // dispose. | |
610 IUndoContext[] contexts = operation.getContexts(); | |
611 for (int j = 0; j < contexts.length; j++) { | |
612 if (contexts[j].matches(context)) { | |
613 operation.removeContext(contexts[j]); | |
614 } | |
615 } | |
616 if (operation.getContexts().length is 0) { | |
617 redoList.remove(cast(Object)operation); | |
618 internalRemove(operation); | |
619 } | |
620 } | |
621 } | |
622 } | |
623 | |
624 /* | |
625 * Flush the undo stack of all operations that have the given context. | |
626 */ | |
627 private void flushUndo(IUndoContext context) { | |
628 if (DEBUG_OPERATION_HISTORY_DISPOSE) { | |
629 Tracing.printTrace("OPERATIONHISTORY", "Flushing undo history for " //$NON-NLS-1$ //$NON-NLS-2$ | |
630 ~ (cast(Object)context).toString ); | |
631 } | |
632 | |
633 // Get all operations that have the context (or one that matches) | |
634 IUndoableOperation[] filtered = filter(undoList, context); | |
635 for (int i = 0; i < filtered.length; i++) { | |
636 IUndoableOperation operation = cast(IUndoableOperation) filtered[i]; | |
637 if (context is GLOBAL_UNDO_CONTEXT | |
638 || operation.getContexts().length is 1) { | |
639 // remove the operation if it only has the context or we are | |
640 // flushing all | |
641 undoList.remove(cast(Object)operation); | |
642 internalRemove(operation); | |
643 } else { | |
644 // remove the reference to the context. | |
645 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786 | |
646 // It is not enough to simply remove the context. There could | |
647 // be one or more contexts that match the one we are trying to | |
648 // dispose. | |
649 IUndoContext[] contexts = operation.getContexts(); | |
650 for (int j = 0; j < contexts.length; j++) { | |
651 if (contexts[j].matches(context)) { | |
652 operation.removeContext(contexts[j]); | |
653 } | |
654 } | |
655 if (operation.getContexts().length is 0) { | |
656 undoList.remove(cast(Object)operation); | |
657 internalRemove(operation); | |
658 } | |
659 } | |
660 } | |
661 /* | |
662 * There may be an open composite. If it has this context, then the | |
663 * context must be removed. If it has only this context or we are | |
664 * flushing all operations, then null it out and notify that we are | |
665 * ending it. We don't remove it since it was never added. | |
666 */ | |
667 ICompositeOperation endedComposite = null; | |
668 synchronized (openCompositeLock) { | |
669 if (openComposite !is null) { | |
670 if (openComposite.hasContext(context)) { | |
671 if (context is GLOBAL_UNDO_CONTEXT | |
672 || openComposite.getContexts().length is 1) { | |
673 endedComposite = openComposite; | |
674 openComposite = null; | |
675 } else { | |
676 openComposite.removeContext(context); | |
677 } | |
678 } | |
679 } | |
680 } | |
681 // notify outside of the synchronized block. | |
682 if (endedComposite !is null) { | |
683 notifyNotOK(endedComposite); | |
684 } | |
685 } | |
686 | |
687 /* | |
688 * Force the redo history for the given context to contain max or less | |
689 * items. | |
690 */ | |
691 private void forceRedoLimit(IUndoContext context, int max) { | |
692 IUndoableOperation[] filtered = filter(redoList, context); | |
693 int size = filtered.length; | |
694 if (size > 0) { | |
695 int index = 0; | |
696 while (size > max) { | |
697 IUndoableOperation removed = cast(IUndoableOperation)filtered[index]; | |
698 if (context is GLOBAL_UNDO_CONTEXT | |
699 || removed.getContexts().length is 1) { | |
700 /* | |
701 * remove the operation if we are enforcing a global limit | |
702 * or if the operation only has the specified context | |
703 */ | |
704 redoList.remove(cast(Object)removed); | |
705 internalRemove(removed); | |
706 } else { | |
707 /* | |
708 * if the operation has multiple contexts and we've reached | |
709 * the limit for only one of them, then just remove the | |
710 * context, not the operation. | |
711 */ | |
712 removed.removeContext(context); | |
713 } | |
714 size--; | |
715 index++; | |
716 } | |
717 } | |
718 } | |
719 | |
720 /* | |
721 * Force the undo history for the given context to contain max or less | |
722 * items. | |
723 */ | |
724 private void forceUndoLimit(IUndoContext context, int max) { | |
725 IUndoableOperation[] filtered = filter(undoList, context); | |
726 int size = filtered.length; | |
727 if (size > 0) { | |
728 int index = 0; | |
729 while (size > max) { | |
730 IUndoableOperation removed = cast(IUndoableOperation)filtered[index]; | |
731 if (context is GLOBAL_UNDO_CONTEXT | |
732 || removed.getContexts().length is 1) { | |
733 /* | |
734 * remove the operation if we are enforcing a global limit | |
735 * or if the operation only has the specified context | |
736 */ | |
737 undoList.remove(cast(Object)removed); | |
738 internalRemove(removed); | |
739 } else { | |
740 /* | |
741 * if the operation has multiple contexts and we've reached | |
742 * the limit for only one of them, then just remove the | |
743 * context, not the operation. | |
744 */ | |
745 removed.removeContext(context); | |
746 } | |
747 size--; | |
748 index++; | |
749 } | |
750 } | |
751 } | |
752 | |
753 /* | |
754 * (non-Javadoc) | |
755 * | |
756 * @see org.eclipse.core.commands.operations.IOperationHistory#getLimit() | |
757 */ | |
758 public int getLimit(IUndoContext context) { | |
759 if (!limits.containsKey(cast(Object)context)) { | |
760 return DEFAULT_LIMIT; | |
761 } | |
762 return (cast(Integer) (limits.get(cast(Object)context))).intValue(); | |
763 } | |
764 | |
765 /* | |
766 * Consult the IOperationApprovers to see if the proposed redo should be | |
767 * allowed. | |
768 */ | |
769 private IStatus getRedoApproval(IUndoableOperation operation, | |
770 IAdaptable info) { | |
771 | |
772 Object[] approverArray = approvers.getListeners(); | |
773 | |
774 for (int i = 0; i < approverArray.length; i++) { | |
775 IOperationApprover approver = cast(IOperationApprover) approverArray[i]; | |
776 IStatus approval = approver.proceedRedoing(operation, this, info); | |
777 if (!approval.isOK()) { | |
778 if (DEBUG_OPERATION_HISTORY_APPROVAL) { | |
779 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
780 "Redo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ | |
781 ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$ | |
782 ~ " approved by " ~ (cast(Object)approval).toString); //$NON-NLS-1$ | |
783 } | |
784 return approval; | |
785 } | |
786 } | |
787 return Status.OK_STATUS; | |
788 } | |
789 | |
790 /* | |
791 * (non-Javadoc) | |
792 * | |
793 * @see org.eclipse.core.commands.operations.IOperationHistory#getRedoHistory(org.eclipse.core.commands.operations.IUndoContext) | |
794 */ | |
795 public IUndoableOperation[] getRedoHistory(IUndoContext context) { | |
796 Assert.isNotNull(cast(Object)context); | |
797 return filter(redoList, context); | |
798 } | |
799 | |
800 /* | |
801 * (non-Javadoc) | |
802 * | |
803 * @see org.eclipse.core.commands.operations.IOperationHistory#getOperation(org.eclipse.core.commands.operations.IUndoContext) | |
804 */ | |
805 public IUndoableOperation getRedoOperation(IUndoContext context) { | |
806 Assert.isNotNull(cast(Object)context); | |
807 synchronized (undoRedoHistoryLock) { | |
808 for (int i = redoList.size() - 1; i >= 0; i--) { | |
809 IUndoableOperation operation = cast(IUndoableOperation) redoList | |
810 .get(i); | |
811 if (operation.hasContext(context)) { | |
812 return operation; | |
813 } | |
814 } | |
815 } | |
816 return null; | |
817 } | |
818 | |
819 /* | |
820 * Consult the IOperationApprovers to see if the proposed undo should be | |
821 * allowed. | |
822 */ | |
823 private IStatus getUndoApproval(IUndoableOperation operation, | |
824 IAdaptable info) { | |
825 | |
826 final Object[] approverArray = approvers.getListeners(); | |
827 | |
828 for (int i = 0; i < approverArray.length; i++) { | |
829 IOperationApprover approver = cast(IOperationApprover) approverArray[i]; | |
830 IStatus approval = approver.proceedUndoing(operation, this, info); | |
831 if (!approval.isOK()) { | |
832 if (DEBUG_OPERATION_HISTORY_APPROVAL) { | |
833 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
834 "Undo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ | |
835 ~ "for operation " ~ (cast(Object)operation ).toString//$NON-NLS-1$ | |
836 ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$ | |
837 } | |
838 return approval; | |
839 } | |
840 } | |
841 return Status.OK_STATUS; | |
842 } | |
843 | |
844 /* | |
845 * (non-Javadoc) | |
846 * | |
847 * @see org.eclipse.core.commands.operations.IOperationHistory#getUndoHistory(org.eclipse.core.commands.operations.IUndoContext) | |
848 */ | |
849 public IUndoableOperation[] getUndoHistory(IUndoContext context) { | |
850 Assert.isNotNull(cast(Object)context); | |
851 return filter(undoList, context); | |
852 } | |
853 | |
854 /* | |
855 * (non-Javadoc) | |
856 * | |
857 * @see org.eclipse.core.commands.operations.IOperationHistory#getUndoOperation(org.eclipse.core.commands.operations.IUndoContext) | |
858 */ | |
859 public IUndoableOperation getUndoOperation(IUndoContext context) { | |
860 Assert.isNotNull(cast(Object)context); | |
861 synchronized (undoRedoHistoryLock) { | |
862 for (int i = undoList.size() - 1; i >= 0; i--) { | |
863 IUndoableOperation operation = cast(IUndoableOperation) undoList | |
864 .get(i); | |
865 if (operation.hasContext(context)) { | |
866 return operation; | |
867 } | |
868 } | |
869 } | |
870 return null; | |
871 } | |
872 | |
873 /* | |
874 * Consult the IOperationApprovers to see if the proposed execution should | |
875 * be allowed. | |
876 * | |
877 * @since 3.2 | |
878 */ | |
879 private IStatus getExecuteApproval(IUndoableOperation operation, | |
880 IAdaptable info) { | |
881 | |
882 final Object[] approverArray = approvers.getListeners(); | |
883 | |
884 for (int i = 0; i < approverArray.length; i++) { | |
885 if ( auto apro = cast(IOperationApprover2)approverArray[i] ) { | |
886 IOperationApprover2 approver = apro; | |
887 IStatus approval = approver.proceedExecuting(operation, this, | |
888 info); | |
889 if (!approval.isOK()) { | |
890 if (DEBUG_OPERATION_HISTORY_APPROVAL) { | |
891 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
892 "Execute not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$ | |
893 ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$ | |
894 ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$ | |
895 } | |
896 return approval; | |
897 } | |
898 } | |
899 } | |
900 return Status.OK_STATUS; | |
901 } | |
902 | |
903 /* | |
904 * Remove the operation by disposing it and notifying listeners. | |
905 */ | |
906 private void internalRemove(IUndoableOperation operation) { | |
907 operation.dispose(); | |
908 notifyRemoved(operation); | |
909 } | |
910 | |
911 /* | |
912 * Notify listeners of an operation event. | |
913 */ | |
914 private void notifyListeners(OperationHistoryEvent event) { | |
915 if ( auto e = cast(IAdvancedUndoableOperation)event.getOperation() ) { | |
916 IAdvancedUndoableOperation advancedOp = e; | |
917 SafeRunner.run(new class(advancedOp) ISafeRunnable { | |
918 IAdvancedUndoableOperation advancedOp_; | |
919 this(IAdvancedUndoableOperation a){ advancedOp_=a;} | |
920 public void handleException(Exception exception) { | |
921 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
922 Tracing.printTrace( | |
923 "OPERATIONHISTORY", //$NON-NLS-1$ | |
924 "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$ | |
925 } | |
926 } | |
927 | |
928 public void run() { | |
929 advancedOp_.aboutToNotify(event); | |
930 } | |
931 }); | |
932 } | |
933 Object[] listenerArray = listeners.getListeners(); | |
934 for (int i = 0; i < listenerArray.length; i++) { | |
935 IOperationHistoryListener listener = cast(IOperationHistoryListener) listenerArray[i]; | |
936 SafeRunner.run(new class(listener) ISafeRunnable { | |
937 IOperationHistoryListener listener_; | |
938 this(IOperationHistoryListener a){ listener_=a; } | |
939 public void handleException(Exception exception) { | |
940 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
941 Tracing.printTrace( | |
942 "OPERATIONHISTORY", //$NON-NLS-1$ | |
943 "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$ | |
944 } | |
945 } | |
946 | |
947 public void run() { | |
948 listener_.historyNotification(event); | |
949 } | |
950 }); | |
951 } | |
952 } | |
953 | |
954 private void notifyAboutToExecute(IUndoableOperation operation) { | |
955 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
956 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_EXECUTE " //$NON-NLS-1$ //$NON-NLS-2$ | |
957 ~ (cast(Object)operation).toString); | |
958 } | |
959 | |
960 notifyListeners(new OperationHistoryEvent( | |
961 OperationHistoryEvent.ABOUT_TO_EXECUTE, this, operation)); | |
962 } | |
963 | |
964 /* | |
965 * Notify listeners that an operation is about to redo. | |
966 */ | |
967 private void notifyAboutToRedo(IUndoableOperation operation) { | |
968 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
969 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_REDO " //$NON-NLS-1$ //$NON-NLS-2$ | |
970 ~ (cast(Object)operation).toString); | |
971 } | |
972 | |
973 notifyListeners(new OperationHistoryEvent( | |
974 OperationHistoryEvent.ABOUT_TO_REDO, this, operation)); | |
975 } | |
976 | |
977 /* | |
978 * Notify listeners that an operation is about to undo. | |
979 */ | |
980 private void notifyAboutToUndo(IUndoableOperation operation) { | |
981 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
982 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_UNDO " //$NON-NLS-1$ //$NON-NLS-2$ | |
983 ~ (cast(Object)operation).toString); | |
984 } | |
985 | |
986 notifyListeners(new OperationHistoryEvent( | |
987 OperationHistoryEvent.ABOUT_TO_UNDO, this, operation)); | |
988 } | |
989 | |
990 /* | |
991 * Notify listeners that an operation has been added. | |
992 */ | |
993 private void notifyAdd(IUndoableOperation operation) { | |
994 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
995 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_ADDED " //$NON-NLS-1$ //$NON-NLS-2$ | |
996 ~ (cast(Object)operation).toString); | |
997 } | |
998 | |
999 notifyListeners(new OperationHistoryEvent( | |
1000 OperationHistoryEvent.OPERATION_ADDED, this, operation)); | |
1001 } | |
1002 | |
1003 /* | |
1004 * Notify listeners that an operation is done executing. | |
1005 */ | |
1006 private void notifyDone(IUndoableOperation operation) { | |
1007 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1008 Tracing.printTrace("OPERATIONHISTORY", "DONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ | |
1009 } | |
1010 | |
1011 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.DONE, | |
1012 this, operation)); | |
1013 } | |
1014 | |
1015 /* | |
1016 * Notify listeners that an operation did not succeed after an attempt to | |
1017 * execute, undo, or redo was made. | |
1018 */ | |
1019 private void notifyNotOK(IUndoableOperation operation) { | |
1020 notifyNotOK(operation, null); | |
1021 } | |
1022 | |
1023 /* | |
1024 * Notify listeners that an operation did not succeed after an attempt to | |
1025 * execute, undo, or redo was made. Include the status associated with the | |
1026 * attempt. | |
1027 * | |
1028 * @since 3.2 | |
1029 */ | |
1030 private void notifyNotOK(IUndoableOperation operation, IStatus status) { | |
1031 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1032 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_NOT_OK " //$NON-NLS-1$ //$NON-NLS-2$ | |
1033 ~ (cast(Object)operation).toString); | |
1034 } | |
1035 | |
1036 notifyListeners(new OperationHistoryEvent( | |
1037 OperationHistoryEvent.OPERATION_NOT_OK, this, operation, status)); | |
1038 } | |
1039 | |
1040 /* | |
1041 * Notify listeners that an operation was redone. | |
1042 */ | |
1043 private void notifyRedone(IUndoableOperation operation) { | |
1044 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1045 Tracing.printTrace("OPERATIONHISTORY", "REDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ | |
1046 } | |
1047 | |
1048 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.REDONE, | |
1049 this, operation)); | |
1050 } | |
1051 | |
1052 /* | |
1053 * Notify listeners that an operation has been removed from the history. | |
1054 */ | |
1055 private void notifyRemoved(IUndoableOperation operation) { | |
1056 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1057 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_REMOVED " //$NON-NLS-1$ //$NON-NLS-2$ | |
1058 ~ (cast(Object)operation).toString); | |
1059 } | |
1060 | |
1061 notifyListeners(new OperationHistoryEvent( | |
1062 OperationHistoryEvent.OPERATION_REMOVED, this, operation)); | |
1063 } | |
1064 | |
1065 /* | |
1066 * Notify listeners that an operation has been undone. | |
1067 */ | |
1068 private void notifyUndone(IUndoableOperation operation) { | |
1069 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1070 Tracing.printTrace("OPERATIONHISTORY", "UNDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$ | |
1071 } | |
1072 | |
1073 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.UNDONE, | |
1074 this, operation)); | |
1075 } | |
1076 | |
1077 /* | |
1078 * Notify listeners that an operation has been undone. | |
1079 */ | |
1080 private void notifyChanged(IUndoableOperation operation) { | |
1081 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) { | |
1082 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_CHANGED " //$NON-NLS-1$//$NON-NLS-2$ | |
1083 ~ (cast(Object)operation).toString); | |
1084 } | |
1085 | |
1086 notifyListeners(new OperationHistoryEvent( | |
1087 OperationHistoryEvent.OPERATION_CHANGED, this, operation)); | |
1088 } | |
1089 | |
1090 /* | |
1091 * (non-Javadoc) | |
1092 * | |
1093 * @see org.eclipse.core.commands.operations.IOperationHistory#redo(org.eclipse.core.commands.operations.IUndoContext, | |
1094 * org.eclipse.core.runtime.IProgressMonitor, | |
1095 * org.eclipse.core.runtime.IAdaptable) | |
1096 */ | |
1097 public IStatus redo(IUndoContext context, IProgressMonitor monitor, | |
1098 IAdaptable info) { | |
1099 Assert.isNotNull(cast(Object)context); | |
1100 IUndoableOperation operation = getRedoOperation(context); | |
1101 | |
1102 // info if there is no operation | |
1103 if (operation is null) { | |
1104 return IOperationHistory.NOTHING_TO_REDO_STATUS; | |
1105 } | |
1106 | |
1107 // error if operation is invalid | |
1108 if (!operation.canRedo()) { | |
1109 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1110 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1111 "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
1112 } | |
1113 | |
1114 return IOperationHistory.OPERATION_INVALID_STATUS; | |
1115 } | |
1116 | |
1117 return doRedo(monitor, info, operation); | |
1118 } | |
1119 | |
1120 /* | |
1121 * (non-Javadoc) | |
1122 * | |
1123 * @see org.eclipse.core.commands.operations.IOperationHistory#redoOperation(org.eclipse.core.commands.operations.IUndoableOperation, | |
1124 * org.eclipse.core.runtime.IProgressMonitor, | |
1125 * org.eclipse.core.runtime.IAdaptable) | |
1126 */ | |
1127 | |
1128 public IStatus redoOperation(IUndoableOperation operation, | |
1129 IProgressMonitor monitor, IAdaptable info) { | |
1130 Assert.isNotNull(cast(Object)operation); | |
1131 IStatus status; | |
1132 if (operation.canRedo()) { | |
1133 status = doRedo(monitor, info, operation); | |
1134 } else { | |
1135 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1136 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1137 "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
1138 } | |
1139 status = IOperationHistory.OPERATION_INVALID_STATUS; | |
1140 } | |
1141 return status; | |
1142 } | |
1143 | |
1144 /* | |
1145 * (non-Javadoc) | |
1146 * | |
1147 * @see org.eclipse.core.commands.operations.IOperationHistory#removeOperationApprover(org.eclipse.core.commands.operations.IOperationApprover) | |
1148 */ | |
1149 public void removeOperationApprover(IOperationApprover approver) { | |
1150 approvers.remove(cast(Object)approver); | |
1151 } | |
1152 | |
1153 /* | |
1154 * (non-Javadoc) | |
1155 * | |
1156 * @see org.eclipse.core.commands.operations.IOperationHistory#removeOperationHistoryListener(org.eclipse.core.commands.operations.IOperationHistoryListener) | |
1157 */ | |
1158 public void removeOperationHistoryListener( | |
1159 IOperationHistoryListener listener) { | |
1160 listeners.remove(cast(Object)listener); | |
1161 } | |
1162 | |
1163 /* | |
1164 * (non-Javadoc) | |
1165 * | |
1166 * @see org.eclipse.core.commands.operations.IOperationHistory#replaceOperation(org.eclipse.core.commands.operations.IUndoableOperation, | |
1167 * org.eclipse.core.commands.operations.IUndoableOperation []) | |
1168 */ | |
1169 public void replaceOperation(IUndoableOperation operation, | |
1170 IUndoableOperation[] replacements) { | |
1171 // check the undo history first. | |
1172 bool inUndo = false; | |
1173 synchronized (undoRedoHistoryLock) { | |
1174 int index = undoList.indexOf(cast(Object)operation); | |
1175 if (index > -1) { | |
1176 inUndo = true; | |
1177 undoList.remove(cast(Object)operation); | |
1178 // notify listeners after the lock on undoList is released | |
1179 ArrayList allContexts = new ArrayList(replacements.length); | |
1180 for (int i = 0; i < replacements.length; i++) { | |
1181 IUndoContext[] opContexts = replacements[i].getContexts(); | |
1182 for (int j = 0; j < opContexts.length; j++) { | |
1183 allContexts.add(cast(Object)opContexts[j]); | |
1184 } | |
1185 undoList.add(index, cast(Object)replacements[i]); | |
1186 // notify listeners after the lock on the history is | |
1187 // released | |
1188 } | |
1189 // recheck all the limits. We do this at the end so the index | |
1190 // doesn't change during replacement | |
1191 for (int i = 0; i < allContexts.size(); i++) { | |
1192 IUndoContext context = cast(IUndoContext) allContexts.get(i); | |
1193 forceUndoLimit(context, getLimit(context)); | |
1194 } | |
1195 } | |
1196 } | |
1197 if (inUndo) { | |
1198 // notify listeners of operations added and removed | |
1199 internalRemove(operation); | |
1200 for (int i = 0; i < replacements.length; i++) { | |
1201 notifyAdd(replacements[i]); | |
1202 } | |
1203 return; | |
1204 } | |
1205 | |
1206 // operation was not in the undo history. Check the redo history. | |
1207 | |
1208 synchronized (undoRedoHistoryLock) { | |
1209 int index = redoList.indexOf(cast(Object)operation); | |
1210 if (index is -1) { | |
1211 return; | |
1212 } | |
1213 ArrayList allContexts = new ArrayList(replacements.length); | |
1214 redoList.remove(cast(Object)operation); | |
1215 // notify listeners after we release the lock on redoList | |
1216 for (int i = 0; i < replacements.length; i++) { | |
1217 IUndoContext[] opContexts = replacements[i].getContexts(); | |
1218 for (int j = 0; j < opContexts.length; j++) { | |
1219 allContexts.add(cast(Object)opContexts[j]); | |
1220 } | |
1221 redoList.add(index, cast(Object)replacements[i]); | |
1222 // notify listeners after we release the lock on redoList | |
1223 } | |
1224 // recheck all the limits. We do this at the end so the index | |
1225 // doesn't change during replacement | |
1226 for (int i = 0; i < allContexts.size(); i++) { | |
1227 IUndoContext context = cast(IUndoContext) allContexts.get(i); | |
1228 forceRedoLimit(context, getLimit(context)); | |
1229 } | |
1230 } | |
1231 // send listener notifications after we release the lock on the history | |
1232 internalRemove(operation); | |
1233 for (int i = 0; i < replacements.length; i++) { | |
1234 notifyAdd(replacements[i]); | |
1235 } | |
1236 } | |
1237 | |
1238 /* | |
1239 * (non-Javadoc) | |
1240 * | |
1241 * @see org.eclipse.core.commands.operations.IOperationHistory#setLimit(org.eclipse.core.commands.operations.IUndoContext, | |
1242 * int) | |
1243 */ | |
1244 public void setLimit(IUndoContext context, int limit) { | |
1245 Assert.isTrue(limit >= 0); | |
1246 /* | |
1247 * The limit checking methods interpret a null context as a global limit | |
1248 * to be enforced. We do not wish to support a global limit in this | |
1249 * implementation, so we throw an exception for a null context. The rest | |
1250 * of the implementation can handle a null context, so subclasses can | |
1251 * override this if a global limit is desired. | |
1252 */ | |
1253 Assert.isNotNull(cast(Object)context); | |
1254 limits.put(cast(Object)context, new Integer(limit)); | |
1255 forceUndoLimit(context, limit); | |
1256 forceRedoLimit(context, limit); | |
1257 | |
1258 } | |
1259 | |
1260 /* | |
1261 * (non-Javadoc) | |
1262 * | |
1263 * @see org.eclipse.core.commands.operations.IOperationHistory#undo(org.eclipse.core.commands.operations.IUndoContext, | |
1264 * org.eclipse.core.runtime.IProgressMonitor, | |
1265 * org.eclipse.core.runtime.IAdaptable) | |
1266 */ | |
1267 public IStatus undo(IUndoContext context, IProgressMonitor monitor, | |
1268 IAdaptable info) { | |
1269 Assert.isNotNull(cast(Object)context); | |
1270 IUndoableOperation operation = getUndoOperation(context); | |
1271 | |
1272 // info if there is no operation | |
1273 if (operation is null) { | |
1274 return IOperationHistory.NOTHING_TO_UNDO_STATUS; | |
1275 } | |
1276 | |
1277 // error if operation is invalid | |
1278 if (!operation.canUndo()) { | |
1279 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1280 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1281 "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
1282 } | |
1283 return IOperationHistory.OPERATION_INVALID_STATUS; | |
1284 } | |
1285 | |
1286 return doUndo(monitor, info, operation); | |
1287 } | |
1288 | |
1289 /* | |
1290 * (non-Javadoc) | |
1291 * | |
1292 * @see org.eclipse.core.commands.operations.IOperationHistory#undoOperation(org.eclipse.core.commands.operations.IUndoableOperation, | |
1293 * org.eclipse.core.runtime.IProgressMonitor, | |
1294 * org.eclipse.core.runtime.IAdaptable) | |
1295 */ | |
1296 public IStatus undoOperation(IUndoableOperation operation, | |
1297 IProgressMonitor monitor, IAdaptable info) { | |
1298 Assert.isNotNull(cast(Object)operation); | |
1299 IStatus status; | |
1300 if (operation.canUndo()) { | |
1301 status = doUndo(monitor, info, operation); | |
1302 } else { | |
1303 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1304 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1305 "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
1306 } | |
1307 status = IOperationHistory.OPERATION_INVALID_STATUS; | |
1308 } | |
1309 return status; | |
1310 } | |
1311 | |
1312 /* | |
1313 * (non-Javadoc) | |
1314 * | |
1315 * @see org.eclipse.core.commands.operations.IOperationHistory#openOperation(org.eclipse.core.commands.operations.ICompositeOperation) | |
1316 */ | |
1317 public void openOperation(ICompositeOperation operation, int mode) { | |
1318 synchronized (openCompositeLock) { | |
1319 if (openComposite !is null && openComposite !is operation) { | |
1320 // unexpected nesting of operations. | |
1321 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1322 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1323 "Open operation called while another operation is open. old: " //$NON-NLS-1$ | |
1324 ~ (cast(Object)openComposite).toString ~ "; new: " ~ (cast(Object)operation).toString); //$NON-NLS-1$ | |
1325 } | |
1326 | |
1327 throw new IllegalStateException( | |
1328 "Cannot open an operation while one is already open"); //$NON-NLS-1$ | |
1329 } | |
1330 openComposite = operation; | |
1331 } | |
1332 if (DEBUG_OPERATION_HISTORY_OPENOPERATION) { | |
1333 Tracing.printTrace("OPERATIONHISTORY", "Opening operation " //$NON-NLS-1$ //$NON-NLS-2$ | |
1334 ~ (cast(Object)openComposite).toString); | |
1335 } | |
1336 | |
1337 if (mode is EXECUTE) { | |
1338 notifyAboutToExecute(openComposite); | |
1339 } | |
1340 } | |
1341 | |
1342 /* | |
1343 * (non-Javadoc) | |
1344 * | |
1345 * @see org.eclipse.core.commands.operations.IOperationHistory#closeOperation(bool, | |
1346 * bool) | |
1347 */ | |
1348 public void closeOperation(bool operationOK, bool addToHistory, | |
1349 int mode) { | |
1350 ICompositeOperation endedComposite = null; | |
1351 | |
1352 synchronized (openCompositeLock) { | |
1353 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) { | |
1354 if (openComposite is null) { | |
1355 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$ | |
1356 "Attempted to close operation when none was open"); //$NON-NLS-1$ | |
1357 return; | |
1358 } | |
1359 } | |
1360 // notifications will occur outside the synchonized block | |
1361 if (openComposite !is null) { | |
1362 if (DEBUG_OPERATION_HISTORY_OPENOPERATION) { | |
1363 Tracing.printTrace("OPERATIONHISTORY", "Closing operation " //$NON-NLS-1$ //$NON-NLS-2$ | |
1364 ~ (cast(Object)openComposite).toString); | |
1365 } | |
1366 endedComposite = openComposite; | |
1367 openComposite = null; | |
1368 } | |
1369 } | |
1370 // any mode other than EXECUTE was triggered by a request to undo or | |
1371 // redo something already in the history, so undo and redo | |
1372 // notification will occur at the end of that sequence. | |
1373 if (endedComposite !is null) { | |
1374 if (operationOK) { | |
1375 if (mode is EXECUTE) { | |
1376 notifyDone(endedComposite); | |
1377 } | |
1378 if (addToHistory) { | |
1379 add(endedComposite); | |
1380 } | |
1381 } else { | |
1382 if (mode is EXECUTE) { | |
1383 notifyNotOK(endedComposite); | |
1384 } | |
1385 } | |
1386 } | |
1387 } | |
1388 | |
1389 /* | |
1390 * (non-Javadoc) | |
1391 * | |
1392 * @see org.eclipse.core.commands.operations.IOperationHistory#operationChanged(org.eclipse.core.commands.operations.IUndoableOperation) | |
1393 */ | |
1394 public void operationChanged(IUndoableOperation operation) { | |
1395 if (undoList.contains(cast(Object)operation) || redoList.contains(cast(Object)operation)) { | |
1396 notifyChanged(operation); | |
1397 } | |
1398 } | |
1399 } |