실행 취소 가능 오퍼레이션

Workbench에 조치를 제공하는 여러 가지 많은 방법을 보았지만 조치의 run() 메소드 구현에 초점을 두지 않았습니다. 메소드의 구조는 질문에 따른 특정 조치에 따라 다르지만, 코드를 실행 취소 가능 오퍼레이션으로 구성하면 조치가 플랫폼 실행 취소 및 재실행 지원에 참여할 수 있습니다.

플랫폼은 org.eclipse.core.commands.operations 패키지에서 실행 취소 가능 오퍼레이션 프레임워크를 제공합니다. 코드를 run() 메소드 안에 구현하여 IUndoableOperation을 작성함으로써 실행 취소 및 재실행에 해당 오퍼레이션을 사용할 수 있습니다. 오퍼레이션을 사용하도록 조치를 변환하는 것은 간단하며, 실행 취소 및 재실행 동작 자체를 구현하는 것과는 별개입니다.

실행 취소 가능 오퍼레이션 쓰기

아주 간단한 예제부터 시작합니다. readme 예제 플러그인에서 제공되는 간단한 ViewActionDelegate를 다시 호출하십시오. 호출될 때 조치는 단순히 실행되었음을 발표하는 대화 상자를 실행합니다.

public void run(org.eclipse.jface.action.IAction action) {
	MessageDialog.openInformation(view.getSite().getShell(),
		MessageUtil.getString("Readme_Editor"),  
		MessageUtil.getString("View_Action_executed")); 
}
오퍼레이션 사용 시, run 메소드가 run 메소드에서 이전에 수행한 작업을 수행하는 오퍼레이션을 작성하며, 실행 취소 및 재실행에 대해 기억할 수 있도록 오퍼레이션 히스토리가 오퍼레이션을 실행하도록 요청할 책임이 있습니다.
public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
오퍼레이션은 run 메소드의 이전 동작을 포함할 뿐 아니라 오퍼레이션에 대한 실행 취소 및 재실행도 포함합니다.
class ReadmeOperation extends AbstractOperation {
	Shell shell;
	public ReadmeOperation(Shell shell) {
		super("Readme Operation");
		this.shell = shell;
	}
	public IStatus execute(IProgressMonitor monitor, IAdaptable info) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),  
		MessageUtil.getString("View_Action_executed")); 
		return Status.OK_STATUS;
	}
	public IStatus undo(IProgressMonitor monitor) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),  
			"Undoing view action"));   
		return Status.OK_STATUS;
	}
	public IStatus redo(IProgressMonitor monitor) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),  
			"Redoing view action"));   
		return Status.OK_STATUS;
	}
}

단순 조치의 경우, 모든 너트 및 볼트 작업을 오퍼레이션 클래스로 이동할 수 있습니다. 이 경우, 이전 조치 클래스를 매개변수화된 단일 조치 클래스로 접는 것이 적절할 수 있습니다. 조치는 단지 실행할 시간이 되면 제공된 오퍼레이션을 실행합니다. 이는 응용프로그램 디자인 결정입니다.

조치가 마법사를 실행할 때 오퍼레이션은 일반적으로 마법사의 performFinish() 메소드나 마법사 페이지의 finish() 메소드의 부분으로 작성됩니다. 오퍼레이션을 사용하기 위해 finish 메소드를 변환하는 것은 run 메소드 변환과 비슷합니다. 이 메소드가 이전에 인라인으로 수행된 작업을 수행하는 오퍼레이션 작성 및 실행을 책임집니다.

오퍼레이션 히스토리

지금까지 오퍼레이션 히스토리를 실제로 설명하지 않고 사용했습니다. 예제 오퍼레이션을 작성하는 코드를 다시 보십시오.

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
오퍼레이션 히스토리란 무엇입니까? IOperationHistory는 모든 실행 취소 가능 오퍼레이션의 추적하는 오브젝트에 대한 인터페이스를 정의합니다. 오퍼레이션 히스토리가 오퍼레이션을 실행할 때 먼저 오퍼레이션을 실행한 후 이를 실행 취소 히스토리에 추가합니다. 오퍼레이션을 실행 취소 및 재실행하려는 클라이언트는 IOperationHistory 프로토콜을 사용하여 해당 작업을 수행합니다.

응용프로그램이 사용하는 오퍼레이션 히스토리는 여러 가지 방법으로 검색할 수 있습니다. 가장 간단한 방법은 OperationHistoryFactory를 사용하는 것입니다.

IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();

또한 Workbench를 사용하여 오퍼레이션 히스토리를 검색할 수도 있습니다. Workbench는 기본 오퍼레이션 히스토리를 구성하며 또한 이를 액세스하기 위한 프로토콜을 제공합니다. 다음 스니펫은 Workbench에서 오퍼레이션 히스토리를 얻는 방법을 보여줍니다.

IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench();
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
오퍼레이션 히스토리를 얻은 후 이를 사용하여 실행 취소 또는 재실행 히스토리를 조회하거나 실행 취소 또는 재실행에 대한 선상에서 다음이 어떤 오퍼레이션인지 확인하거나 특정 오퍼레이션을 실행 취소 또는 재실행할 수 있습니다. 클라이언트는 히스토리에 대한 변경에 관한 알림을 받기 위해 IOperationHistoryListener를 추가할 수 있습니다. 기타 프로토콜을 사용하여 클라이언트가 히스토리에 대한 한계를 설정하거나 특정 오퍼레이션에 대한 변경에 대해 리스너에 알릴 수 있습니다. 프로토콜을 상세히 알아보기 전에 실행 취소 컨텍스트를 이해해야 합니다.

실행 취소 컨텍스트

오퍼레이션은 작성될 때 원래 오퍼레이션이 수행된 사용자 컨텍스트를 기술하는 실행 취소 컨텍스트가 지정됩니다. 실행 취소 컨텍스트는 일반적으로 실행 취소 가능 오퍼레이션을 시작한 보기 또는 편집기에 종속됩니다. 예를 들어, 편집기 내에서 수행된 변경사항은 종종 편집기에만 해당됩니다. 이 경우, 편집기는 자체의 고유한 실행 취소 컨텍스트를 작성하고 이 컨텍스트를 오퍼레이션에 지정해야 합니다. 그러면 히스토리에 추가됩니다. 이 방법에서 편집기에서 수행된 모든 오퍼레이션은 로컬로 간주되고 반 개인용입니다. 공유 모델에 대해 작동하는 보기 및 편집기는 종종 조작하고 있는 모델에 관련된 실행 취소 컨텍스트를 사용합니다. 보다 일반적인 실행 취소 컨텍스트를 사용하면 하나의 보기 또는 편집기에서 수행된 오퍼레이션이 동일한 모델에 작동하는 다른 보기 또는 편집기에서의 실행 취소에 사용 가능할 수 있습니다.

실행 취소 컨텍스트는 상대적으로 동작이 단순합니다. IUndoContext에 대한 프로토콜은 아주 작습니다. 컨텍스트의 주 역할은 특정 오퍼레이션을 다른 실행 취소 컨텍스트에서 작성되는 오퍼레이션과 구별하기 위해 해당 실행 취소 컨텍스트에 속하는 것으로 "태그"하는 것입니다. 그러면 오퍼레이션 히스토리가 실행된 모든 실행 취소 가능 오퍼레이션의 글로벌 히스토리를 추적할 수 있으며, 보기와 편집기가 실행 취소 컨텍스트를 사용하여 특정 시점에 대해 히스토리를 필터링할 수 있습니다.

실행 취소 컨텍스트는 실행 취소 가능 오퍼레이션을 작성하는 플러그인에 의해 작성되거나 또는 API를 통해 액세스될 수 있습니다. 예를 들어 Workbench는 Workbench 전체의 오퍼레이션에 사용할 수 있는 실행 취소 컨텍스트에 대한 액세스를 제공합니다. 어떤 방법으로 얻든지 간에 실행 취소 컨텍스트는 오퍼레이션이 작성될 때 지정되어야 합니다. 다음 스니펫은 readme 플러그인의 ViewActionDelegate가 Workbench 전체의 컨텍스트를 그의 오퍼레이션에 지정하는 방법을 보여줍니다.

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench();
	IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
	IUndoContext undoContext = workbench.getOperationSupport().getUndoContext();
	operation.addContext(undoContext);
	operationHistory.execute(operation, null, null);
}

실행 취소 컨텍스트를 사용하는 이유는 무엇입니까? 개별 보기 및 편집기에 대해 별도의 오퍼레이션 히스토리를 사용하지 않는 이유는 무엇입니까? 개별 오퍼레이션 히스토리 사용은 모든 특정 보기 또는 편집기가 고유한 개인용 실행 취소 히스토리를 유지보수하고 실행 취소가 응용프로그램에서 글로벌 의미가 없다고 가정합니다. 이것은 일부 응용프로그램에 대해 적합할 수 있으며, 이런 경우 각 보기나 편집기가 고유한 개별 실행 취소 컨텍스트를 작성해야 합니다. 다른 응용프로그램은 오퍼레이션이 시작한 보기나 편집기와 상관 없이 모든 사용자 오퍼레이션에 적용되는 글로벌 실행 취소를 구현하기 원할 수 있습니다. 이 경우에는 Workbench 컨텍스트가 히스토리에 오퍼레이션을 추가하는 모든 플러그인에 의해 사용되어야 합니다.

더 복잡한 응용프로그램에서는 실행 취소가 완전히 로컬이거나 완전히 글로벌이지 않습니다. 대신, 실행 취소 컨텍스트 사이를 넘나드는 컨텍스트가 있습니다. 이것은 오퍼레이션에 복수 컨텍스트를 지정하여 달성할 수 있습니다. 예를 들어 IDE Workbench 보기가 모든 작업공간을 조작하고 작업공간을 그의 실행 취소 컨텍스트로 간주할 수 있습니다. 작업공간의 특정 자원에 대해 열린 편집기는 그의 오퍼레이션을 대부분 로컬로 간주할 수 있습니다. 그러나 편집기 안에서 수행되는 오퍼레이션은 사실상 특정 자원과 작업공간 전체에 모두 영향을 줄 수 있습니다. (이 케이스의 좋은 예제는 JDT 리팩토링 지원으로, Java 요소에 대한 구조적 변경이 소스 파일을 편집하는 중에 발생하도록 허용합니다.) 이러한 경우에는 작업공간을 조작하는 보기뿐 아니라, 편집기 자체에서 실행 취소를 수행할 수 있도록 오퍼레이션에도 실행 취소 컨텍스트를 추가할 수 있는 것이 유용합니다.

이제 실행 취소 컨텍스트가 어떤 역할을 하는지 이해했으므로, IOperationHistory에 대한 프로토콜을 다시 조사할 수 있습니다. 다음 스니펫은 일부 컨텍스트에 실행 취소를 수행하는 데 사용됩니다.

IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
         try {
	IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo);
} catch (ExecutionException e) {
	// handle the exception 
}
히스토리는 주어진 컨텍스트를 갖고 그 자체를 실행 취소하도록 요청하는 가장 최근에 수행된 오퍼레이션을 얻습니다. 기타 프로토콜을 사용하여 컨텍스트에 대한 전체 실행 취소 또는 재실행 히스토리를 가져오거나 특정 컨텍스트에서 실행 취소 또는 재실행될 오퍼레이션을 찾을 수 있습니다. 다음 스니펫은 특정 컨텍스트에서 실행 취소될 오퍼레이션에 대한 레이블을 얻습니다.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
String label = history.getUndoOperation(myContext).getLabel();

글로벌 실행 취소 컨텍스트 IOperationHistory.GLOBAL_UNDO_CONTEXT를 사용하여 글로벌 실행 취소 히스토리를 참조할 수 있습니다. 즉, 특정 컨텍스트에 관계없이 히스토리의 모든 오퍼레이션을 참조합니다. 다음 스니펫은 글로벌 실행 취소 히스토리를 확보합니다.

IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT);

오퍼레이션이 오퍼레이션 히스토리 프로토콜을 사용하여 실행, 실행 취소 또는 다시 실행될 때마다 클라이언트가 오퍼레이션 수행에 필요할 수 있는 진행 모니터 및 추가 UI 정보를 제공할 수 있습니다. 이 정보는 오퍼레이션 자체에 전달됩니다. 원래 예제에서 readme 조치는 대화 상자를 여는 데 사용할 수 있는 쉘 매개변수로 오퍼레이션을 구성했습니다. 쉘을 오퍼레이션에 저장하는 대신, 더 좋은 방법은 오퍼레이션을 실행하는 데 필요한 모든 UI 정보를 제공하는 execute, undo 및 redo 메소드에 매개변수를 전달하는 것입니다. 이들 매개변수는 오퍼레이션 자체에 전달됩니다.

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation();
	...
	operationHistory.execute(operation, null, infoAdapter);
}
infoAdapter는 최소한 대화 상자를 실행할 때 사용될 수 있는 을 제공할 수 있는 IAdaptable입니다. 본 예제 오퍼레이션은 이 매개변수를 다음과 같이 사용합니다.
	public IStatus execute(IProgressMonitor monitor, IAdaptable info) {
		if (info != null) {
			Shell shell = (Shell)info.getAdapter(Shell.class);
			if (shell != null) {
		MessageDialog.openInformation(shell,
		MessageUtil.getString("Readme_Editor"),  
		MessageUtil.getString("View_Action_executed")); 
            return Status.OK_STATUS;
		}
	}
		// do something else...
}

실행 취소 및 재실행 조치 핸들러

플랫폼은 특정 컨텍스트에 대해 실행 취소 및 재실행 지원을 제공하기 위해 보기 및 편집기에서 구성할 수 있는 표준 실행 취소 및 재실행 대상 재지정 가능 조치 핸들러를 제공합니다. 이 조치 핸들러가 작성될 때 컨텍스트가 지정되므로 오퍼레이션 히스토리가 해당 특정 보기에 적합한 방법으로 필터링됩니다. 조치 핸들러는 의심이 가는 현재 오퍼레이션을 표시하기 위해 실행 취소 및 재실행 레이블을 갱신하며 오퍼레이션 히스토리에 진행 모니터 및 UI 정보를 제공하고 선택적으로 현재 오퍼레이션이 유효하지 않을 때 히스토리를 제거합니다. 조치 핸들러를 작성하고 이들을 글로벌 실행 취소 및 재실행 조치에 지정하는 조치 그룹이 편의상 제공됩니다.

new UndoRedoActionGroup(this.getSite(), undoContext, true);
마지막 매개변수는 실행 취소 및 재실행에 대해 현재 사용 가능한 오퍼레이션이 유효하지 않을 때 지정된 컨텍스트에 대한 실행 취소 및 재실행 히스토리가 폐기되어야 하는지 여부를 표시하는 부울입니다. 이 매개변수의 설정은 제공되는 실행 취소 컨텍스트 및 해당 컨텍스트가 있는 오퍼레이션이 사용하는 유효성 검증 전략에 관계됩니다.

응용프로그램 실행 취소 모델

앞에서 실행 취소 컨텍스트를 사용하여 여러 가지 종류의 응용프로그램 실행 취소 모델을 구현하는 방법을 보았습니다. 오퍼레이션에 하나 이상의 컨텍스트를 지정하는 기능은 응용프로그램이 각 보기 또는 편집기에 완전히 로컬이거나, 완전히 모든 플러그인 사이에 글로벌이거나 그 사이의 모델인 실행 취소 전략을 구현할 수 있게 합니다. 실행 취소 및 재실행과 관련된 또 다른 디자인 의사결정은 임의의 오퍼레이션이 항상 실행 취소 또는 재실행될 수 있는지 아니면 모델이 완전히 선형이어서 가장 최근의 오퍼레이션만이 실행 취소 및 재실행에 대해 고려되는지 여부입니다.

IOperationHistory는 허용되는 사항을 판별하는 것을 개별 구현에 맡기는 유연한 실행 취소 모델을 허용하는 프로토콜을 정의합니다. 지금까지 본 실행 취소 및 재실행 프로토콜은 특정 실행 취소 컨텍스트에서 실행 취소 및 재실행에 사용 가능한 하나의 내재된 오퍼레이션이 있다고 가정합니다. 클라이언트가 히스토리에서의 위치와 상관 없이 특정 오퍼레이션을 실행할 수 있게 하는 추가 프로토콜이 제공됩니다. 응용프로그램에 적합한 모델을 구현할 수 있도록 오퍼레이션 히스토리를 구성할 수 있습니다. 이것은 오퍼레이션이 실행 취소 또는 재실행되기 전에 모든 실행 취소 또는 재실행 요청을 사전에 승인하는 데 사용되는 인터페이스으로 완료됩니다.

오퍼레이션 승인자

IOperationApprover는 특정 오퍼레이션에 대한 실행 취소 및 재실행을 승인하는 프로토콜을 제공합니다. 오퍼레이션 승인자는 오퍼레이션 히스토리에 설치됩니다. 특정 오퍼레이션 승인자는 다시 유효성에 대해 모든 오퍼레이션을 확인하거나, 특정 컨텍스트의 오퍼레이션을 확인하거나, 사용자에게 오퍼레이션에서 예상하지 못한 상태가 발견된 경우를 묻는 프롬프트를 표시합니다. 다음 스니펫은 응용프로그램이 모든 오퍼레이션에 대해 선형 실행 취소 모델을 강제 실행하도록 오퍼레이션 히스토리를 구성할 수 있는 방법을 보여줍니다.
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// set an approver on the history that will disallow any undo that is not the most recent operation
history.addOperationApprover(new LinearUndoEnforcer());

이 경우에 프레임워크가 제공하는 오퍼레이션 승인자인 LinearUndoEnforcer가 히스토리에 설치됩니다. 이는 모든 실행 취소 컨텍스트에서 가장 최근에 수행되거나 실행 취소된 오퍼레이션이 아닌 오퍼레이션의 실행 취소나 다시 실행을 방지하기 위해서입니다.

또 다른 오퍼레이션 승인자 LinearUndoViolationUserApprover는 동일 상태를 발견하고 사용자에게 오퍼레이션이 계속되도록 허용해야 하는지 여부를 묻는 프롬프트를 표시합니다. 이 오퍼레이션 승인자는 특정 Workbench 파트에 설치할 수 있습니다.

IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// set an approver on this part that will prompt the user when the operation is not the most recent.
IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart);
history.addOperationApprover(approver);

플러그인 개발자는 응용프로그램 특정 실행 취소 모델과 승인 전략을 구현하기 위해 마음대로 고유한 오퍼레이션 승인자를 개발하고 설치할 수 있습니다.

실행 취소 및 IDE Workbench

오퍼레이션 히스토리 및 Workbench 실행 취소 컨텍스트에 액세스하기 위해 Workbench 프로토콜을 사용하는 코드 스니펫을 보았습니다. 이것은 Workbench에서 얻을 수 있는 IWorkbenchOperationSupport를 사용하여 달성됩니다. Workbench 전반의 실행 취소 컨텍스트의 개념은 상당히 일반적입니다. Workbench 실행 취소 컨텍스트가 암시하는 특정 범위가 무엇인지, 어떤 보기 또는 편집기가 실행 취소 지원을 제공할 때 해당 Workbench 컨텍스트를 사용하는지 판별하는 것은 Workbench 응용프로그램의 몫입니다.

Eclipse IDE Workbench의 경우 Workbench 실행 취소 컨텍스트가 IDE Workbench 전체에 영향을 주는 모든 오퍼레이션에 지정되어야 합니다. 이 컨텍스트는 자원 네비게이터 같은 작업공간을 조작하는 보기에 의해 사용됩니다. IDE Workbench는 Workbench 실행 취소 컨텍스트를 리턴하는 IUndoContext에 대한 작업공간에 어댑터를 설치합니다. 이 모델 기반 등록은 작업공간을 조작하는 플러그인이 headless이고 어떤 Workbench 클래스도 참조하지 않은 경우에도 적절한 실행 취소 컨텍스트를 얻을 수 있도록 합니다.

// get the operation history
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// obtain the appropriate undo context for my model
IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class);
if (workspaceContext != null) {
	// create an operation and assign it the context
}

기타 플러그인은 모델 기반 실행 취소 컨텍스트를 등록하기 위해 이 동일한 기법을 사용하는 것이 좋습니다.