Vratné operace

Věnovali jsme se mnoha různým způsobům vkládání akcí do pracovní plochy Workbench, dosud jsme však neprobrali implementaci metody run() akce. Mechaniky metody závisí na určité potřebné akci, nicméně strukturování kódu jako vratné operace umožňuje akci podílet se na podpoře akcí Zpět nebo Znovu platformy.

Platforma poskytuje rámec vratných operací v balíčku org.eclipse.core.commands.operations. Implementujete-li uvnitř metody run() kód vytvoření IUndoableOperation, lze pro tuto operaci použít akce Zpět a Znovu. Převod akce k použití operací je snadné a nezasahuje do chování akcí Zpět a Znovu.

Zápis vratné operace

Začneme velmi jednoduchým příkladem. Připomeňme si jednoduchý prvek ViewActionDelegate ve vzorovém modulu plug-in README. Při vyvolání akce jednoduše spustí dialogové okno, které oznámí provedení této akce.

public void run(org.eclipse.jface.action.IAction action) {
	MessageDialog.openInformation(view.getSite().getShell(),
		MessageUtil.getString("Readme_Editor"),
		MessageUtil.getString("View_Action_executed"));
}
Při použití operací metoda run vytvoří operaci, jež provede práci, kterou dosud prováděla metoda run, a požádá historii operací o provedení operace tak, aby mohla být zapamatována pro akce Zpět a Znovu.
public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
Operace zapouzdřuje činnosti obsažené dosud v metodě run a akce Zpět a Znovu pro operaci.
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;
	}
}

Pro jednoduché akce je možné přesunout všechny úkony do třídy operace. V takovém případě může být vhodné sbalit dosavadní třídy akcí do jediné parametrizované třídy akce. Akce provede zadanou operaci ve správném momentu. Toto rozhodnutí se spíše provádí v rámci návrhu aplikace.

Spustí-li akce průvodce, vytvoří se operace typicky jako součást metody průvodce performFinish() nebo metody stránky průvodce finish(). Úprava metody finish pro použití operací je obdobou úpravy metody run. Metoda zajišťuje vytvoření a provedení operace, která provádí činnosti dosud zajišťované vloženým prvkem (inline).

Historie operací

Dosud jsme historii operací používali, aniž bychom ji skutečně vyložili. Vraťme se opět ke kódu, který vytváří naši vzorovou operaci.

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation(
		view.getSite().getShell()); 
	...
	operationHistory.execute(operation, null, null);
}
Co to vlastně je historie operací? IOperationHistory definuje rozhraní pro objekt, který sleduje všechny vratné operace. Provádí-li historie operací určitou operaci, nejprve ji provede a poté ji uloží do historie operací Zpět. Klienti požadující operace Zpět a Znovu používají pro tento účel protokol IOperationHistory.

Historii operací, kterou používá aplikace, lze načítat různými způsoby. Nejjednodušším způsobem je použití OperationHistoryFactory.

IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();

K načtení historie operací lze použít pracovní plochu. Pracovní plocha zahrnuje konfiguraci výchozí historie operací a rovněž poskytuje protokol přístupu k historii operací. Následující úsek kódu předvádí způsob získání historie operací z pracovní plochy.

IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench();
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
Po získání historie operací ji lze použít k dotazu na historii akcí Zpět nebo Znovu, ke zjištění, která operace Zpět nebo Znovu je na řadě a dále k operací Zpět nebo Znovu konkrétních operací. Klienti mohou přidat prvek IOperationHistoryListener za účelem přijímání upozornění na změny historie. Jiný protokol klientům umožňuje nastavovat omezení historie a odesílat listenerům upozornění na změny určité operace. Dříve, než se budeme podrobně věnovat tomuto protokolu, musíme pochopit kontext akce Zpět.

Kontexty akce Zpět

Při vytvoření operace se jí přiřazuje kontext akce Zpět, který popisuje uživatelský kontext, ve kterém byla původní operace provedena. Kontext akce Zpět v typickém případě závisí na pohledu nebo editoru, který vyvolal vratnou operaci. Například změny provedené uvnitř editoru jsou často lokálními změnami platnými pro tento editor. V tomto případě by měl editor vytvořit svůj vlastní kontext akce Zpět a přiřadit jej operacím, které přidává do historie. Tímto způsobem jsou všechny operace prováděné v editoru považovány za lokální a částečně soukromé. Sdílené editory či pohledy části používají kontext akcí Zpět, který patří ke sdílenému modelu. Při použití obecnějšího kontextu akce Zpět lze operace provedené v určitém pohledu nebo editoru vrátit v jiném pohledu nebo editoru, který používá stejný model.

Kontexty akce Zpět mají poměrně jednoduché chování; protokol IUndoContext je skutečně velmi jednoduchý. Hlavní rolí kontextu je "označit" určitou operaci jako náležející do tohoto kontextu akce Zpět za účelem odlišení od operací vytvořených v jiných kontextech akce Zpět. Toto historii operací umožňuje sledování globální historie všech provedených vratných operací, zatímco pohledy a editory mohou s použitím kontextu akce Zpět filtrovat historii pro určitý účel.

Kontexty akce Zpět lze vytvářet modulem plug-in, který vytváří vratné operace, nebo k nim lze přistupovat prostřednictvím rozhraní API. Například pracovní plocha poskytne přístup ke kontextu akce Zpět, který lze použít pro operace v celé pracovní ploše. Po jejich získání by měly být kontexty akce Zpět přiřazovány operacím při jejich vytváření. Následující úsek kódu předvádí způsob, kterým mohou moduly plug-in souboru README ViewActionDelegate přiřazovat kontext globální pro pracovní plochu svým operacím.

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);
}

Proč vůbec používat kontexty akce Zpět? Proč nepoužít oddělené historie operací pro samostatné pohledy a editory? Používání samostatných historií operací předpokládá, že jednotlivé pohledy a editory uchovávají svou vlastní soukromou historii akce Zpět a dále že akce Zpět nemá v aplikaci žádný globální význam. Tato metoda může být vhodná pro určité aplikace a v takovýchto případech by každý pohled a editor měl udržovat svůj vlastní samostatný kontext akce Zpět. Ostatní aplikace mohou vyžadovat implementaci globální akce Zpět, která se uplatní pro všechny uživatelské operace bez ohledu na pohled či editor, ve kterém vznikly. V tomto případě by měly všechny moduly plug-in vkládající operace do historie používat kontext pracovní plochy.

V komplikovanějších aplikacích není akce Zpět ani přísně lokální, ani přísně globální. Místo toho se kontexty akce Zpět určitým způsobem překrývají. Toho lze dosáhnout přiřazením několika kontextů určité operaci. Například pohled pracovní plochy IDE může manipulovat s celou pracovní plochou a uplatňovat svůj kontext akce Zpět pro pracovní prostor. Editor otevřený na určitém prostředku v pracovním prostoru může své operace ve většině případů považovat za lokální. Nicméně operace provedené v editoru mohou ve skutečnosti ovlivnit daný prostředek i celý pracovní prostor. (Dobrým příkladem této situace je podpora opětovných deklarací JDT, jež umožňuje změnu struktury prvku Java při editaci zdrojového souboru.) V takovýchto případech je vhodné mít možnost přiřadit operaci oba kontexty akce Zpět tak, aby bylo možné akci Zpět provádět v samotném editoru i v ovlivněných pohledech pracovní plochy.

Nyní již víme, co je kontext akce Zpět, a proto se můžeme vrátit k protokolu IOperationHistory. Následující úsek kódu se používá k provedení akce Zpět v některém kontextu:

IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
  try {
	IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo);
} catch (ExecutionException e) {
	// ošetření výjimky
}
Historie získá poslední provedenou operaci, které je přiřazen kontext, a vyzve ji k akci Zpět. Jiný protokol lze použít k získání kompletní historie operací Zpět a Znovu pro kontext nebo pro nalezení operace, která má být vrácena nebo zopakována v určitém kontextu. Následující úsek kódu získává štítek operace, která bude v určitém kontextu vrácena.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();
String label = history.getUndoOperation(myContext).getLabel();

Globální kontext akce Zpět, IOperationHistory.GLOBAL_UNDO_CONTEXT, lze použít k odkazu na globální historii akce Zpět. Jde o odkaz na všechny operace v historii bez ohledu na jejich konkrétní kontext. Následující úsek kódu získává globální historii operací Zpět.

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

Vždy při provádění operace, operace Zpět nebo Znovu s použitím protokolu historie operací mohou klienti poskytnout monitor průběhu a veškeré přídavné informace uživatelského rozhraní, které mohou být zapotřebí k provedení operace. Tato informace se předává samotné operaci. V našem původním příkladu akce README vytvořila operaci s parametrem příkazového řádku, kterou lze použit k otevření dialogového okna. Místo ukládání příkazového řádku v operaci je vhodnější předat parametry do metod akcí Zpět a Znovu, které poskytnou veškeré informace uživatelského rozhraní nezbytné ke spuštění operace. Tyto parametry jsou předávány samotné operaci.

public void run(org.eclipse.jface.action.IAction action) {
	IUndoableOperation operation = new ReadmeOperation();
	...
	operationHistory.execute(operation, null, infoAdapter);
}
infoAdapter je IAdaptable, který může přinejmenším poskytnout nadstavbu, kterou lze použít ke spouštění dialogových oken. Operace našeho příkladu může tento parametr použít níže uvedeným způsobem:
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;
		}
	}
	// další akce...
}

Popisovače akcí Zpět a Znovu

Platforma poskytuje standardní popisovače přecílitelných akcí Zpět a Znovu, které lze konfigurovat pohledy a editory za účelem poskytnutí podpory akcí Zpět a Znovu pro jejich konkrétní kontexty. Při vytvoření je popisovači akcí přiřazen kontext a historie operací je tak filtrována způsobem vhodným pro tento konkrétní pohled. Popisovače akcí zajišťují aktualizaci štítků akcí Zpět a Znovu za účelem zobrazení dotyčné aktuální operace, vhodného monitoru průběhu a poskytnutí informací uživatelského rozhraní historii operací, a dále (volitelně) vyčištění historie, pokud aktuální operace není platná. Pro usnadnění je poskytnuta skupina akcí, která vytváří popisovače akcí a přiřazuje tyto popisovače ke globálním akcím Zpět a Znovu.

new UndoRedoActionGroup(this.getSite(), undoContext, true);
Poslední parametr má logickou hodnotu a určuje, zda mají být historie akcí Zpět a Znovu pro určitý kontext likvidovány, pokud operace aktuálně dostupná vrácení nebo zopakování není platná. Nastavení tohoto parametru závisí na poskytnutém kontextu akce Zpět a na strategii ověření použité operacemi s tímto kontextem.

Aplikační modely akce Zpět

Již jsme se věnovali způsobu, kterým lze kontexty akce Zpět operací použít k implementaci různých aplikačních modelů operace Zpět. Schopnost přiřazovat operacím jeden nebo několik kontextů aplikacím umožňuje implementovat strategie akce Zpět, které jsou přísně lokální pro jednotlivé pohledy či editory, přísně globální pro všechny moduly plug-in nebo určité smíšené typy. Dalším rozhodnutím návrhu aplikace je rozhodnutí, zda má být k dispozici akce Zpět nebo Znovu všech operací ve kterémkoli momentu, nebo zda má být použit přísně lineární model, který akci Zpět nebo Znovu zpřístupňuje pouze pro poslední provedenou operaci.

IOperationHistory definuje protokol, který umožňuje pružné modely akce Zpět, tj. ponechává na jednotlivých implementacích, aby určily, co je povoleno. Protokol akce Zpět a Znovu, který jsme dosud viděli, předpokládá, že v určitém kontextu akce Zpět je k dispozici pouze jediná zahrnutá operace dostupná pro akci Zpět nebo Znovu. K dispozici je další protokol, který klientům umožňuje provést určitou operaci bez ohledu na její umístění v historii. Historii operací lze konfigurovat tak, aby bylo možné implementovat model vhodný pro danou aplikaci. Toto se provádí prostřednictvím rozhraní, které se používá k předběžnému schválení veškerých požadavků akce Zpět nebo Znovu ještě před vlastním vrácením nebo zopakováním operace.

Schvalovatelé operací

IOperationApprover definuje protokol schvalování akce Zpět nebo Znovu určité operace. Schvalovatel operace je instalován v historii operací. Určití schvalovatelé operací mohou naopak kontrolovat platnost všech operací, provádět výlučnou kontrolu operací určitých kontextů nebo komunikovat s uživatelem při výskytu neočekávaných podmínek v operaci. Následující úsek kódu předvádí způsob, kterým aplikace může konfigurovat historii operací za účelem vynucení lineárního modelu akce Zpět pro veškeré operace.
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// nastavuje schvalovatele v historii, který zamezí v akci Zpět veškerých operací mimo poslední provedenou operaci
history.addOperationApprover(new LinearUndoEnforcer());

V tomto případě je schvalovatel operace poskytnutý platformou, LinearUndoEnforcer, instalován do historie, aby zamezil akci Zpět nebo Znovu veškerých operací, které nejsou poslední provedenou nebo zopakovanou operací ve všech kontextech akce Zpět této operace.

Jiný schvalovatel operace, LinearUndoViolationUserApprover, rozpoznává tentýž stav a dotazuje se uživatele, zda má být povolena další činnost operace. Tento schvalovatel operace může být instalován v určité části pracovní plochy.

IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// nastavuje v této části schvalovatele, který zobrazí dotaz uživateli, pokud operace není poslední provedenou operací.
IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart);
history.addOperationApprover(approver);

Vývojáři modulů plug-in mohou vyvíjet a instalovat své vlastní schvalovatele operací za účelem implementace aplikačních modelů akce Zpět a strategií schvalování.

Akce Zpět a IDE Workbench

Viděli jsme úseky kódu, které používají protokol pracovní plochy pro přístup do historie operací a ke kontextu akce Zpět pracovní plochy. Toto zajišťuje prvek IWorkbenchOperationSupport, který lze získat z pracovní plochy. Pojem globálního kontextu akce Zpět pracovní plochy je obecný. Aplikace pracovní plochy musí stanovit konkrétní rozsah implikovaný kontextem akce Zpět pracovní plochy a dále pohledy a editory, které používají kontext pracovní plochy při poskytování podpory akce Zpět.

V případě pracovní plochy IDE Eclipse workbench by měl být kontext přiřazen všem operacím, které ovlivňují celkový pracovní prostor IDE. Tento kontext používají pohledy manipulující s pracovním prostorem, tj. například navigátor prostředků. Pracovní plocha IDE workbench instaluje adaptér do pracovního prostoru pro IUndoContext, který vrací kontext akce Zpět pracovní plochy. Tato modelová registrace modulům manipulujícím z pracovním prostorem umožňuje získávat vhodný kontext akce Zpět, a to dokonce i v případech, kdy jsou bez hlaviček a neodkazují na žádnou třídu pracovní plochy.

// získává historii operací
IOperationHistory history = OperationHistoryFactory.getOperationHistory();

// získává vhodný kontext akce Zpět pro můj model
IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class);
if (workspaceContext != null) {
	// vytvořit operaci a přiřadit jí kontext
}

V ostatních modulech plug-in doporučujeme použít tentýž postup modelové registrace kontextů akce Zpět.