W dokumentacji jest omówionych wiele sposobów na wniesienie akcji do środowiska roboczego. Ta sekcja opisuje nieomówioną jeszcze implementację metody run() akcji. Szczegóły tej metody zależą od przeznaczenia akcji, ale można nadać jej określoną strukturę operacji z możliwością cofania, co pozwoli na korzystanie z obsługiwanego przez platformę mechanizmu cofania i przywracania akcji.
Platforma udostępnia środowisko z możliwością cofania operacji w pakiecie org.eclipse.core.commands.operations. Aby operację można było cofać lub przywracać, metoda run() powinna implementować kod tworzący obiekt klasy IUndoableOperation. Przekształcenie akcji w taki sposób, aby można ją było cofać lub przywracać jest proste, poza implementacją samych działań cofania i przywracania.
Na początek przedstawiony zostanie bardzo prosty przykład. Korzysta on z klasy ViewActionDelegate, dostępnej w przykładowym module dodatkowym readme. Wywołanie tej akcji powoduje po prostu wyświetlenie okna dialogowego, informującego o wykonaniu akcji.
public void run(org.eclipse.jface.action.IAction action) { MessageDialog.openInformation(view.getSite().getShell(), MessageUtil.getString("Readme_Editor"), MessageUtil.getString("View_Action_executed")); }Jeśli używane są operacje, metoda run jest odpowiedzialna za utworzenie operacji wykonującej czynności, które wcześniej były wykonywane przez metodę run, wykonanie tej operacji i dodanie jej do historii operacji, dzięki czemu możliwe będzie wykonywanie operacji cofania i przywracania.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }Ta operacja obudowywuje zadania poprzednio przypisane metodzie run oraz obsługę cofania i przywracania operacji.
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"), "Cofanie akcji widoku")); return Status.OK_STATUS; } public IStatus redo(IProgressMonitor monitor) { MessageDialog.openInformation(shell, MessageUtil.getString("Readme_Editor"), "Przywracanie akcji widoku")); return Status.OK_STATUS; } }
W przypadku prostych akcji możliwe jest umieszczenie wszystkich szczegółów implementacji w klasie operacji. W takiej sytuacji odpowiednim działaniem może być zastąpienie wcześniejszych klas akcji pojedynczą klasą akcji, która będzie parametryzowana. Akcja powinna po prostu wykonywać określoną operację w odpowiednim momencie. Zależy to w dużym stopniu od decyzji projektanta aplikacji.
Jeśli akcja uruchamia kreatora, implementacja operacji jest zwykle tworzona jako fragment metody performFinish() kreatora lub metody finish() strony kreatora. Przekształcenie metody finish tak, żeby korzystała z operacji wykonuje się podobnie, jak analogiczne zmiany w metodzie run. Metoda ta jest odpowiedzialna za utworzenie i wykonanie operacji, której działanie było wcześniej po prostu częścią kodu.
Dotychczas nie zostało podane wyjaśnienie terminu historia operacji. Oto ponownie kod tworzący przykładową operację:
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation( view.getSite().getShell()); ... operationHistory.execute(operation, null, null); }Czym jest historia operacji? Interfejs IOperationHistory definiuje interfejs obiektu, który zajmuje się przechowywaniem informacji o wszystkich operacjach z możliwością cofania. Taki obiekt po wykonaniu danej operacji zapisuje ją w historii dla opcji cofania. Cofanie lub przywracanie operacji odbywa się przy użyciu protokołu IOperationHistory.
Do historii operacji używanej przez aplikację można uzyskać dostęp na wiele sposobów. Najprostszy z nich to skorzystanie z klasy OperationHistoryFactory.
IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
Środowisko robocze może również zostać użyte do wydobycia historii operacji. Środowisko robocze konfiguruje domyślną historię aplikacji i udostępnia protokół dostępu do niej. Poniższy fragment kodu przedstawia sposób pobierania historii operacji ze środowiska roboczego.
IWorkbench workbench = view.getSite().getWorkbenchWindow().getWorkbench(); IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory();Po pobraniu historii operacji może ona posłużyć do przeszukiwania historii operacji cofania lub przywracania, do sprawdzenia, która operacja jest pierwsza w kolejności cofania lub przywracania oraz do cofnięcia lub przywrócenia wybranych operacji. Klienci mogą dodawać interfejs IOperationHistoryListener w celu odbierania powiadomień o zmianach w historii. Inny protokół umożliwia klientom ustawianie ograniczeń historii lub powiadamianie funkcji nasłuchiwania o zmianach dotyczących wybranych operacji. Przed szczegółowym opisem tego protokołu nastąpi omówienie kontekstu cofania.
Do tworzonej operacji jest przypisywany kontekst cofania, opisujący kontekst użytkownika, w którym została wykonana oryginalna operacja. Kontekst cofania zwykle zależy od widoku lub edytora, w którym została wykonana operacja. Na przykład zmiany wykonywane wewnątrz edytora są często traktowane jako lokalne względem tego edytora. W takim przypadku edytor powinien utworzyć własny kontekst operacji cofania i przypisać ten kontekst do operacji, które dodaje do historii. Operacje wykonywane wewnątrz edytora są w ten sposób traktowane jako lokalne, czyli w pewien sposób prywatne. Edytory lub widoki, które działają na podstawie modelu współużytkowanego, często używają kontekstu cofania odwołującego się do modelu, którym manipulują. Stosowanie bardziej ogólnego kontekstu cofania mogłoby powodować, że operacje wykonane na modelu przez jeden widok lub edytor można by było cofać w innym widoku lub edytorze, korzystającym z tego samego modelu.
Zachowanie kontekstów cofania jest dość proste, a protokół interfejsu IUndoContext jest minimalny. Podstawową rolą kontekstu jest "zaznaczenie" konkretnej operacji jako należącej do danego kontekstu cofania. W ten sposób można ją odróżnić od operacji wykonanych w innych kontekstach. Mechanizm ten umożliwia śledzenie globalnej historii wszystkich wykonanych operacji z możliwością cofania, podczas gdy widoki i edytory mogą odpowiednio filtrować historię w poszukiwaniu konkretnych perspektyw za pomocą kontekstów cofania.
Konteksty cofania mogą być tworzone przez moduł dodatkowy tworzący operacje z możliwością cofania. Są także dostępne przez odpowiednie mechanizmy interfejsu API. Przykładowo środowisko robocze zapewnia dostęp do kontekstu cofania, który może być używany dla operacji obejmujących całe środowisko. Niezależnie od sposobu ich uzyskiwania, konteksty cofania powinny być przypisywane tworzonym operacjom. Poniższy fragment kodu pokazuje, w jaki sposób klasa ViewActionDelegate modułu dodatkowego readme mogłaby przypisywać kontekst środowiska roboczego do swoich operacji.
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); }
Dlaczego w ogóle korzystać z kontekstów cofania? Dlaczego nie używać osobnych historii operacji dla oddzielnych widoków i edytorów? Aby rozdzielić historie operacji należałoby założyć, że każdy widok lub edytor przechowuje własną historię, a cofanie operacji wewnątrz widoku lub edytora nie ma żadnego znaczenia dla całej aplikacji. Może to być prawdą w niektórych przypadkach. Wtedy jest zalecane, aby każdy widok lub edytor tworzył własny kontekst cofania. Inne aplikacje mogą potrzebować globalnie działającej funkcji cofania, dotyczącej wszystkich operacji wykonanych przez użytkownika i niezależnej od widoku lub edytora, z których te operacje pochodzą. W takim przypadku kontekst środowiska roboczego powinien być używany przez wszystkie moduły dodatkowe, które dodają operacje do historii.
Bardziej skomplikowane aplikacje używają funkcji cofania w sposób, który nie jest ani ściśle lokalny ani globalny. Tworzą one pewne połączenie kontekstów cofania. Takie zachowanie uzyskuje się, przypisując wiele kontekstów do jednej operacji. Na przykład widok IDE środowiska roboczego może operować całym obszarem roboczym i traktować go jako swój kontekst cofania. Edytor otwarty dla konkretnego zasobu obszaru roboczego może traktować większość swoich operacji jako lokalne. Operacje wykonywane wewnątrz edytora mogą jednak niekiedy mieć wpływ zarówno na dany zasób, jak i na cały obszar roboczy. Przykładem takiego działania jest obsługa refaktoryzacji JDT, która zezwala na zmiany struktury elementu języka Java w trakcie edycji pliku źródłowego. W takich przypadkach przydatna jest możliwość przypisania do operacji dwóch kontekstów cofania, tak aby cofanie mogło zostać wykonane zarówno z poziomu edytora, jak i widoków, które współpracują z obszarem roboczym.
Po wyjaśnieniu istoty działania kontekstu cofania, czas na dokładniejsze omówienie protokołu interfejsu IOperationHistory. Poniższy fragment kodu służy do wykonania operacji cofnięcia w pewnym kontekście:
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); try { IStatus status = operationHistory.undo(myContext, progressMonitor, someInfo); } catch (ExecutionException e) { // obsługa wyjątku }Historia uzyskuje informacje o najnowszej operacji wykonanej w podanym kontekście i żąda od tej operacji, aby cofnęła swoje działanie. Inny protokół jest używany w celu uzyskania całej historii cofania lub przywracania dla danego kontekstu lub do wyszukania operacji, która może zostać cofnięta lub przywrócona w wybranym kontekście. Poniższy fragment kodu pobiera etykietę operacji, która zostanie cofnięta w danym kontekście.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); String label = history.getUndoOperation(myContext).getLabel();
Przy użyciu globalnego kontekstu cofania IOperationHistory.GLOBAL_UNDO_CONTEXT można odwoływać się do globalnej historii cofania, to znaczy do wszystkich operacji w historii niezależnie od ich specyficznych kontekstów. Poniższy fragment kodu pokazuje, w jaki sposób można pobrać globalną historię cofania.
IOperationHistory operationHistory = workbench.getOperationSupport().getOperationHistory(); IUndoableOperation [] undoHistory = operationHistory.getUndoHistory(IOperationHistory.GLOBAL_UNDO_CONTEXT);
Zawsze, gdy operacja jest wykonywana, cofana lub przywracana przy użyciu protokołu historii operacji, klienci udostępniają monitor postępu i wszystkie dodatkowe informacje interfejsu użytkownika potrzebne do prawidłowego wykonania tej operacji. Informacje te są przekazywane do samej operacji. W pierwszym przykładzie akcja readme tworzy operację z parametrem powłoki, który może zostać użyty do otwarcia okna dialogowego. Zamiast zapisywać obiekt powłoki w operacji, lepiej jest przekazać parametry udostępniające wszelkie potrzebne informacje pochodzące z interfejsu użytkownika do metod odpowiedzialnych za wykonanie, cofnięcie i przywrócenie operacji. Parametry te zostaną przekazane dalej, do samej operacji.
public void run(org.eclipse.jface.action.IAction action) { IUndoableOperation operation = new ReadmeOperation(); ... operationHistory.execute(operation, null, infoAdapter); }Adapter infoAdapter to obiekt interfejsu IAdaptable, który dostarcza co najmniej powłokę używaną przy uruchamianiu okien dialogowych. Przykładowa operacja używa tego parametru w następujący sposób:
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; } } // inne operacje... }
Platforma udostępnia standardowe procedury obsługi akcji o zmiennym celu dotyczące cofania i przywracania, które mogą być konfigurowane przez widoki i edytory w taki sposób, aby obsługiwały cofanie i przywracanie w odpowiednim kontekście. Podczas tworzenia procedury obsługi akcji jest do niej przypisywany kontekst, dzięki czemu historia operacji może być filtrowana w sposób odpowiedni dla konkretnego widoku. Procedury obsługi akcji zajmują się aktualizacją etykiet cofania i przywracania, aby wskazywały one na odpowiednie operacje. Udostępniają też historii operacji monitor postępu i inne informacje interfejsu użytkownika, a opcjonalnie mogą również czyścić historię (gdy bieżąca operacja jest niepoprawna). Dla wygody użytkowników dostarczana jest grupa akcji tworzących procedury obsługi akcji i przypisujących je do globalnych akcji cofania i przywracania.
new UndoRedoActionGroup(this.getSite(), undoContext, true);Ostatni parametr to wartość boolowska wskazująca, czy historie cofania i przywracania danego kontekstu należy utylizować w przypadku, gdy operacja aktualnie dostępna do cofnięcia lub przywrócenia jest niepoprawna. Ustawienie tego parametru jest związane z udostępnionym kontekstem cofania i ze strategią sprawdzania poprawności używaną przez operacje w tym kontekście.
Dotychczas omówione zostały zastosowania kontekstów cofania w implementacjach różnych modeli cofania. Możliwość przypisania do operacji jednego lub wielu kontekstów pozwala aplikacjom implementować ściśle lokalne (ograniczone do widoku lub edytora), ściśle globalne (obejmujące wszystkie moduły dodatkowe), a także pośrednie modele strategii cofania. Inny wybór dotyczący cofania i przywracania jest związany z dostępnością operacji do cofnięcia lub przywrócenia. Projektując aplikację należy ustalić, czy będzie możliwe cofanie lub przywracanie tylko ostatniej operacji, czy też będzie można wybrać dowolną operację zapisaną w historii.
Interfejs IOperationHistory definiuje protokół, który pozwala na stosowanie elastycznych modeli cofania i pozostawia w gestii konkretnych implementacji ustalenie dostępnych opcji. Dotychczas przedstawiony protokół cofania i przywracania zakładał, że w danym kontekście istnieje tylko jedna operacja dostępna do cofnięcia lub przywrócenia. Dostarczany jest także dodatkowy protokół umożliwiający klientom wykonanie konkretnej operacji niezależnie od jej pozycji w historii. Historia operacji może być skonfigurowana tak, aby obsługiwała model odpowiedni dla danej aplikacji. W tym celu korzysta się z interfejsu pozwalającego wstępnie zatwierdzić każde żądanie cofnięcia lub przywrócenia operacji, zanim zostanie ono faktycznie spełnione.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // ustawienie obiektu zatwierdzającego w historii, który umożliwi cofnięcie tylko ostatniej operacji history.addOperationApprover(new LinearUndoEnforcer());
W tym przypadku udostępniany przez środowisko obiekt zatwierdzający operację - LinearUndoEnforcer - został zainstalowany w historii, aby zapobiegać cofaniu lub przywracaniu tych operacji, które nie są ostatnimi wykonanymi lub cofniętymi operacjami we wszystkich kontekstach cofania.
Inny obiekt zatwierdzający operacje - LinearUndoViolationUserApprover - wykrywa tę samą sytuację i wyświetla pytanie, czy należy kontynuować wykonywanie operacji. Ten obiekt zatwierdzający operacje może być instalowany w określonej części środowiska roboczego.
IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // ustawienie obiektu zatwierdzającego w tej części, która poinformuje użytkownika w sytuacji, gdy operacja nie jest ostatnią wykonaną operacją IOperationApprover approver = new LinearUndoViolationUserApprover(myUndoContext, myWorkbenchPart); history.addOperationApprover(approver);
Programiści modułów dodatkowych mogą swobodnie tworzyć i instalować swoje własne obiekty zatwierdzające operacje na potrzeby implementacji specyficznych dla aplikacji modeli cofania i strategii zatwierdzania.
Powyżej zostały już przedstawione fragmenty kodu używające protokołu środowiska roboczego do uzyskiwania dostępu do historii operacji i kontekstu cofania środowiska roboczego. Korzysta się w tym celu z interfejsu IWorkbenchOperationSupport, który można uzyskać ze środowiska roboczego. Pojęcie kontekstu cofania obejmującego całe środowisko robocze jest dość ogólne. To aplikacja środowiska roboczego określa, jaki konkretnie zasięg jest rozumiany przez "całe środowisko robocze", i które widoki lub edytory mają korzystać z kontekstu środowiska roboczego do obsługi cofania.
W przypadku środowiska IDE platformy Eclipse kontekst cofania środowiska roboczego powinien być przypisywany do każdej operacji, która wpływa na pracę obszaru roboczego IDE. Ten kontekst jest używany przez widoki zmieniające obszar roboczy, takie jak nawigator zasobów. Środowisko robocze IDE instaluje w obszarze roboczym adapter dla interfejsu IUndoContext, który zwraca kontekst cofania środowiska roboczego. Taka rejestracja oparta na modelu pozwala uzyskać prawidłowy kontekst cofania modułom dodatkowym zmieniającym obszar roboczy, nawet jeśli nie są nadzorowane i nie odwołują się do żadnej klasy środowiska roboczego.
// pobranie historii operacji IOperationHistory history = OperationHistoryFactory.getOperationHistory(); // uzyskanie odpowiedniego kontekstu cofania dla modelu IUndoContext workspaceContext = (IUndoContext)ResourcesPlugin.getWorkspace().getAdapter(IUndoContext.class); if (workspaceContext != null) { // utworzenie operacji i przypisywanie do niej kontekstu }Także w przypadku innych modułów dodatkowych zaleca się korzystanie z tej metody przy rejestrowaniu kontekstów cofania opartych na modelach.