Számos különböző módot láttunk a műveletek munkaterületre hozzáadásával kapcsolatban, azonban még nem tekintettük át egy művelet run() metódusának megvalósítását. A metódus mechanikája az adott művelettől függ, de a kód visszavonható műveletként struktúrálása lehetővé teszi, hogy a művelet részt vegyen a visszavonási és újravégrehajtási műveletek támogatásában.
A platform egy visszavonható műveletek keretrendszert biztosít az org.eclipse.core.commands.operations csomagban. Egy run() metódus kódjának IUndoableOperation felület létrehozása céljából megvalósításával a művelet elérhetővé tehető a visszavonás és újravégrehajtás műveletek számára. Egy tevékenység műveletek magától érthetődő módon használatára átalakítása független a visszavonás és újravégrehajtás műveletek viselkedésétől.
Egy nagyon egyszerű példával kezdjük. Hívja vissza a tájékoztató (readme) bedolgozó példájában biztosított egyszerű ViewActionDelegate elemet. A tevékenység meghívásakor egyszerűen elindít egy párbeszédablakot, amely jelzi, hogy végrehajtásra került.
public void run(org.eclipse.jface.action.IAction action) { MessageDialog.openInformation(view.getSite().getShell(), MessageUtil.getString("Readme_Editor"), MessageUtil.getString("View_Action_executed")); }A műveleteket használó run metódus felelős egy olyan művelet létrehozásáért, amely elvégzi a korábban a run metódusban elvégzett feladatot, valamint kéri, hogy a művelettörténet végrehajtsa a műveletet, így ez megjegyezhető a visszavonáshoz és újra végrehajtáshoz.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }A művelet magába foglalja a run metódus régi viselkedését, ahogy a visszavonás és újraújravégrehajtás műveleteket is.
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; } }
Egyszerű műveletek esetén az alapvető feladatok átvihetők a művelet osztályba. Ebben az esetben érdemes lehet a korábbi tevékenységosztályokat egy paraméterezett tevékenységosztályba összerakni. A tevékenység egyszerűen végrehajtaná a megadott műveletet a futás idejében. Ez főként alkalmazástervezési döntés.
Amikor egy művelet elindít egy varázslót, akkor a művelet jellemzően a varázsló performFinish() metódusának vagy egy varázslóoldal finish() metódusának részeként kerül létrehozásra. A finish metódus műveletek használatára átalakítása hasonló a run metódus átalakításához. A metódus felelős egy olyan művelet létrehozásáért és végrehajtásáért, amely a feladatot korábban végzi el belsőleg.
Már használtunk művelettörténetet anélkül, hogy elmagyaráztuk volna. Tekintsük meg újra a kódot, amely létrehozza a példaműveletet.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }Mire való a művelettörténet? Az IOperationHistory határozza meg azon objektum felületét, amely az összes visszavonható műveletet nyomonköveti. Amikor egy művelettörténet végrehajt egy műveletet, akkor először a műveletet hajtja végre, majd hozzáadja azt a visszavonási történethez. Az ügyfelek, akik vissza szeretnék vonni vagy újra végre kívánják hajtani a műveleteket, ezt az IOperationHistory protokoll használatával tehetik meg.
Egy alkalmazás által használt művelettörténet számos módon lekérhető. A legegyszerűbb módszer az OperationHistoryFactory használata.
IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
A munkaterület is használható a művelettörténet lekérésére. A munkaterület beállítja az alapértelmezett művelettörténetet és egy protokollt biztosít annak eléréséhez. Az alábbi részlet azt mutatja be, hogy a művelettörténet hogyan szerezhető meg a munkaterületről.
IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench(); IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();Ha egy művelettörténet lekérésre került, akkor az a visszavonás és újravégrehajtás műveletek történetének lekérdezésére, a visszavonás vagy újravégrehajtás művelet soron következő műveletének kiderítésére, illetve adott műveletek visszavonására vagy újravégrehajtására használható. Az ügyfelek egy IOperationHistoryListener felületet vehetnek fel, ha a történet változásairól kívánnak értesítést kapni. Más protokoll lehetővé teszi az ügyfelek számára a történethez korlátok beállítását vagy egy értesítés küldését egy adott művelet változásairól a figyelők számára. Mielőtt részletesen megnéznénk a protokollt, meg kell értenünk a visszavonási környezetet.
Egy művelet a létrehozásakor egy visszavonási környezethez kerül hozzárendelésre, amely azt a felhasználói környezetet írja le, amelyen az eredeti művelet végrehajtásra került. A visszavonási környezet jellemzően attól a nézettől vagy szerkesztőtől függ, amelyből ered a visszavonható művelet. A szerkesztőn belüli módosítások gyakran csak a szerkesztőre vonatkoznak. Ebben az esetben a szerkesztőnek létre kell hoznia egy saját visszavonás kontextust és hozzá kell rendelnie ezt az előzményhez adandó műveletekhez. Így a szerkesztőben végrehajtott összes műveletet helyiként és félprivátként veszi figyelembe a rendszer. A megosztott modellen működő szerkesztők és nézetek gyakran olyan visszavonási környezetet használnak, amely a kezelt modellel kapcsolatos. Egy általánosabb visszavonási környezet használatával egy nézet vagy szerkesztő által végrehajtott műveletek más olyan nézetben vagy szerkesztőben rendelkezésre álló visszavonás esetén is elérhetők lehetnek, amely ugyanazon a modellen működik.
A visszavonási környezetek működése viszonylag egyszerű. Az IUndoContext protokollja meglehetősen minimális. Egy környezet fő szerepe egy adott művelet "megjelölése" ezen visszavonási környezethez tartozóként a más visszavonási környezetekben létrehozott műveletektől megkülönböztetés érdekében. Ez lehetővé teszi a művelettörténet számára az összes végrehajtott visszavonható művelet globális történetének nyomonkövetését, amíg a nézetek és szerkesztők szűrhetik a visszavonási környezetet használó nézet adott pontjának történetét.
A visszavonási környezeteket a visszavonható műveleteket előállító bedolgozó hozhatja létre, vagy API-n keresztül érhetők el. Például a munkaterület hozzáférést biztosít egy visszavonási környezethez, amely a munkaterületre kiterjedő műveletekhez használható. Akárhogyis kerülnek megszerzésre, a visszavonási környezeteket hozzá kell rendelni egy művelet létrehozásakor. Az alábbi részlet azt mutatja be, hogy a readme bedolgozó ViewActionDelegate eleme hogyan rendel egy munkaterületre kiterjedő környezetet a műveleteihez.
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); }
Miért használunk visszavonási környezeteket? Miért nem különálló művelettörténeteket használunk a nézetekhez és szerkesztőkhöz? A különálló művelettörténetek használata azt feltételezi, hogy minden adott nézet vagy szerkesztő a saját visszavonási történetét tartja karban és, hogy a visszavonásnak nincs globális vonatkozása az alkalmazásban. Ez néhány alkalmazás esetén megfelelő lehet, és ezekben az esetekben minden egyes nézetnek vagy szerkesztőnek a saját különálló visszavonási környezetét kell létrehoznia. Más alkalmazások esetén szükség lehet az összes felhasználói műveletre vonatkozó olyan globális visszavonás megvalósítására, amely független attól, hogy a nézet vagy szerkesztő honnan származik. Ebben az esetben a munkaterület környezetét kell használnia az összes olyan bedolgozónak, amely műveleteket ad hozzá a történethez.
Az ennél is összetettebb alkalmazásokban a visszavonás nem csak helyi és nem csak globális. Ehelyett valamilyen átfedés van a visszavonási környezetek között. Ez egy művelethez több környezet hozzárendelésével érhető el. Például egy IDE munkaterület-nézet kezeli az egész munkaterületet és a munkaterületet használja visszavonási környezetként. A munkaterület egy adott erőforrásával nyitott szerkesztő a műveleteit legtöbbször helyiként használja. Azonban a szerkesztőben végrehajtott műveletek az adott erőforrást és a munkaterületet is nagy mértékben érintik. (Erre az esetre jó példa a JDT átdolgozási támogatás, amely lehetővé teszi egy Java elem szerkezeti módosításait a forrásfájl szerkesztése közben.) Ezen esetekben hasznos, ha mindkét visszavonási környezet hozzáadható a művelethez, hogy a visszavonás magából a szerkesztőből és a munkaterületet kezelő nézetekből is végrehajtható legyen.
Már tudjuk, hogy mit csinál a visszavonási környezet, ezért megtekinthetjük az IOperationHistory protokollját. Az alábbi részlet egy visszavonás azonos környezetben végrehajtására használható:
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); try { IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo); } catch (ExecutionException e) { // kivételkezelés }A történet le fogja kérdezni azt a legújabban végrehajtott műveletet, amely az adott környezettel rendelkezik és a saját visszavonását kéri. Más protokoll használható egy környezet teljes visszavonási vagy újravégrehajtási műveleteinek történetének lekérésére vagy azon művelet megtalálására, amely visszavonásra vagy újravégrehajtásra fog kerülni egy adott környezetben. Az alábbi részlet egy adott környezetben visszavonásra kerülő művelet címkéjét szerzi meg.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); String label = history.getUndoOperation(myContext).getLabel();
A globális visszavonási környezet, az IOperationHistory.GLOBAL_UNDO_CONTEXT, a globális visszavonás történetre hivatkozhat. Azaz az előzményben lévő összes műveletre, az adott környezettől függetlenül. A következő részlet a globális visszavonási előzményt kérdezi le.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT);
Minden esetben, amikor egy művelet végrehajtásra, visszavonásra vagy újra végrehajtásra kerül, a művelettörténet protokoll használatával, az ügyfelek egy folyamatfigyelőt és további UI információkat biztosíthatnak, amelyekre a művelet végrehajtásához szükség lehet. Ezen információk magához a művelethez kerülnek átadásra. Ez eredeti példánkban a readme művelet egy műveletet állított össze egy shell paraméterrel, amely a párbeszédablak megnyitására használható. A shell paraméter műveletben tárolása helyett jobb megközelítés azon paraméterek execute, undo és redo metódusokhoz átadása, amelyek a művelet futásához szükséges UI információkat biztosítják. Ezen paraméterek magához a művelethez kerülnek átadásra.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation(); ... operationHistory.execute(operation, null, infoAdapter); }Az infoAdapter egy IAdaptable, amely minimálisan biztosítja a Shell elemet, amely a párbeszédablakok indítására használható. A példaműveletünk az alábbiak szerint használná ezt a paramétert:
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; } } // valami más végrehajtása... }
A platform általános visszavonási és újravégrehajtási áttervezhető műveletkezelőket biztosít, amelyeket a nézetek és szerkesztők állíthatnak be az adott környezetükkel kapcsolatos visszavonás és újravégrehajtás támogatása érdekében. A műveletkezelő létrehozásakor egy környezet kerül hozzárendelésre, így a művelettörténet olyan módon kerül szűrésre, amely megfelel az adott nézetnek. A műveletkezelők végzik a visszavonási és az újravégrehajtási címkék frissítését az aktuális művelet megjelenítése céljából a megfelelő folyamatfigyelő és UI információk művelettörténet számára biztosításával és a történet nem kötelező módon ritkításával érvénytelen aktuális művelet esetén. Egy műveletkezelőket létrehozó és azokat a globális visszavonási és újravégrehajtási műveletekhez hozzárendelő műveletcsoportot biztosít a rendszer a kényelem érdekében.
new UndoRedoActionGroup(this.getSite(), undoContext, true);Az utolsó paraméter logikai, amellyel az jelezhető, hogy a megadott környezet visszavonási és újravégrehajtási történeteit el kell-e dobni, amikor a visszavonáshoz vagy újravégrehajtáshoz aktuálisan elérhető művelet érvénytelen. Ezen paraméter beállítása a biztosított visszavonási környezethez és a műveletek által ezen környezethez használt érvényesítési stratégiához kapcsolódik.
Korábban megnéztük, hogy hogyan használhatók a visszavonási környezetek az alkalmazás-visszavonási modellek különféle változatainak megvalósítására. A környezet műveletekhez hozzárendelésének képessége lehetővé teszi az alkalmazások számára a nézethez vagy szerkesztőhöz viszonyítva csak helyi, a bedolgozók közti csak globális vagy valahol a kettő között lévő visszavonási stratégiák megvalósítását. A másik tervezési döntés, amely a visszavonást és az újravégrehajtást érinti, annak eldöntése, hogy bármely művelet bármikor visszavonható vagy újravégrehajtható-e (azaz, hogy a modell csak lineáris-e) kizárólag a legújabb műveletek visszavonásra vagy újravégrehajtásra szem előtt tartásával.
Az IOperationHistory felület egy protokollt határoz meg, amely rugalmas visszavonási modelleket tesz lehetővé azzal, hogy annak eldöntését az egyes megvalósításokra bízza, hogy mi megengedett. Az eddig látott visszavonási és újravégrehajtási protokoll azt feltételezi, hogy csak egyetlen kapcsolódó művelet érhető el a visszavonás vagy újravégrehajtás számára egy adott visszavonási környezetben. Egy kiegészítő protokollt is biztosít a rendszer annak lehetővé tételére, hogy az ügyfelek egy bizonyos műveletet végrehajthassanak függetlenül a művelet történetben elfoglalt helyétől. A művelettörténet beállítható úgy, hogy egy alkalmazás számára megfelelő modell megvalósítható legyen. Ez egy felülettel oldható meg, amely a visszavonási és újravégrehajtási kérések azelőtti jóváhagyására használható, mielőtt a művelet visszavonásra vagy újravégrehajtásra kerül.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // jóváhagyó beállítása a történeten, amely letilt minden olyan visszavonást, amely nem a legújabb műveletre vonatkozik history.addOperationApprover(new LinearUndoEnforcer());
Ebben az esetben a LinearUndoEnforcer, keretrendszer által biztosított műveletjóváhagyó kerül telepítésre a történeten az olyan művelet visszavonásának és újravégrehajtásának megakadályozása érdekében, amely nem a legutoljára végrehajtott vagy visszavont művelet minden visszavonási környezetben.
Másik műveletjóváhagyó, a LinearUndoViolationUserApprover észleli ugyanazt a helyzetet és értesíti a felhasználót, hogy a műveletet folytatását engedélyezni kell-e. Ez a műveletjóváhagyó egy adott munkaterületrészen telepíthető.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // jóváhagyó beállítása ezen a részen, amely értesíti a felhasználót, ha a művelet nem a legújabb. IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart); history.addOperationApprover(approver);
A bedolgozófejlesztők szabadon fejleszthetnek és telepíthetnek saját műveletjóváhagyókat alkalmazás-specifikus visszavonási modellek és jóváhagyási stratégiák megvalósítása érdekében.
Láttuk olyan kódrészleteket, amelyek munkaterület-protokollokat használnak a művelettörténet és a munkaterület visszavonási környezetének eléréséhez. Ez az IWorkbenchOperationSupport felület használatával érhető el, amely a munkaterületről szerezhető meg. A munkaterületre kiterjedő visszavonási környezet ötlete egészen általános. A munkaterület-alkalmazástól függ annak eldöntése, hogy milyen adott hatókört jelent a munkaterület visszavonási környezete és mely nézetek vagy szerkesztők használják a munkaterület környezetét a visszavonási támogatás biztosításakor.
Eclipse IDE munkaterület esetén a munkaterület visszavonási környezetét hozzá kell rendelni azon műveletekhez, amelyek nagyban befolyásolják az IDE munkaterületet. Ezt a környezetet olyan nézetek használják, amelyek a munkaterületet kezelik (például: Erőforrás-navigátor). Az IDE munkaterület egy illesztőt telepít a munkaterületre az IUndoContext számára, amely a munkaterület visszavonási környezetét adja vissza. Ez a modell alapú bejegyzés lehetővé teszi a bedolgozók számára a megfelelő visszavonási környezet bejegyzését még akkor is, ha nem jelennek meg és nem hivatkoznak egy munkaterület-osztályra sem.
// a művelettörténet lekérése IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // a modell számára megfelelő visszavonási környezet lekérése IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class); if (workspaceContext != null) { // egy művelet létrehozása és a környezethez hozzárendelése }Más bedolgozók támogatást élveznek ugyanezen eljárás használatára a modell alapú visszavonási környezetek bejegyezésére.