/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.view.controller; import java.util.EventObject; import java.util.Hashtable; import java.util.Map; import java.util.Stack; import java.util.Vector; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import org.openflexo.ApplicationContext; import org.openflexo.components.ProgressWindow; import org.openflexo.foundation.DefaultFlexoEditor; import org.openflexo.foundation.FlexoException; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.foundation.action.FlexoAction; import org.openflexo.foundation.action.FlexoAction.ExecutionStatus; import org.openflexo.foundation.action.FlexoActionEnableCondition; import org.openflexo.foundation.action.FlexoActionFinalizer; import org.openflexo.foundation.action.FlexoActionInitializer; import org.openflexo.foundation.action.FlexoActionRedoFinalizer; import org.openflexo.foundation.action.FlexoActionRedoInitializer; import org.openflexo.foundation.action.FlexoActionType; import org.openflexo.foundation.action.FlexoActionUndoFinalizer; import org.openflexo.foundation.action.FlexoActionUndoInitializer; import org.openflexo.foundation.action.FlexoActionVisibleCondition; import org.openflexo.foundation.action.FlexoExceptionHandler; import org.openflexo.foundation.action.FlexoGUIAction; import org.openflexo.foundation.action.FlexoUndoableAction; import org.openflexo.foundation.action.UndoManager; import org.openflexo.foundation.dm.DMObject; import org.openflexo.foundation.ie.IEObject; import org.openflexo.foundation.rm.DefaultFlexoResourceUpdateHandler; import org.openflexo.foundation.rm.FlexoProject; import org.openflexo.foundation.rm.ResourceUpdateHandler; import org.openflexo.foundation.utils.FlexoDocFormat; import org.openflexo.foundation.utils.FlexoProgress; import org.openflexo.foundation.utils.FlexoProgressFactory; import org.openflexo.foundation.view.ViewObject; import org.openflexo.foundation.view.action.ActionSchemeActionType; import org.openflexo.foundation.wkf.WKFObject; import org.openflexo.foundation.ws.WSObject; import org.openflexo.localization.FlexoLocalization; import org.openflexo.logging.FlexoLogger; import org.openflexo.module.FlexoModule; import org.openflexo.module.Module; import org.openflexo.module.ModuleLoader; import org.openflexo.module.ModuleLoadingException; public class InteractiveFlexoEditor extends DefaultFlexoEditor { private static final Logger logger = FlexoLogger.getLogger(InteractiveFlexoEditor.class.getPackage().getName()); private static final boolean WARN_MODEL_MODIFICATIONS_OUTSIDE_FLEXO_ACTION_LAYER = false; private final UndoManager _undoManager; private ScenarioRecorder _scenarioRecorder; private final Hashtable<FlexoAction<?, ?, ?>, Vector<FlexoModelObject>> _createdAndNotNotifiedObjects; private final Hashtable<FlexoAction<?, ?, ?>, Vector<FlexoModelObject>> _deletedAndNotNotifiedObjects; private Stack<FlexoAction<?, ?, ?>> _currentlyPerformedActionStack = null; private Stack<FlexoAction<?, ?, ?>> _currentlyUndoneActionStack = null; private Stack<FlexoAction<?, ?, ?>> _currentlyRedoneActionStack = null; private final FlexoProgressFactory _progressFactory; private final ApplicationContext applicationContext; private Map<FlexoModule, ControllerActionInitializer> actionInitializers; public InteractiveFlexoEditor(ApplicationContext applicationContext, FlexoProject project) { super(project); this.applicationContext = applicationContext; actionInitializers = new Hashtable<FlexoModule, ControllerActionInitializer>(); _undoManager = new UndoManager(); if (ScenarioRecorder.ENABLE) { _scenarioRecorder = new ScenarioRecorder(); } _createdAndNotNotifiedObjects = new Hashtable<FlexoAction<?, ?, ?>, Vector<FlexoModelObject>>(); _deletedAndNotNotifiedObjects = new Hashtable<FlexoAction<?, ?, ?>, Vector<FlexoModelObject>>(); _currentlyPerformedActionStack = new Stack<FlexoAction<?, ?, ?>>(); _currentlyUndoneActionStack = new Stack<FlexoAction<?, ?, ?>>(); _currentlyRedoneActionStack = new Stack<FlexoAction<?, ?, ?>>(); _progressFactory = new FlexoProgressFactory() { @Override public FlexoProgress makeFlexoProgress(String title, int steps) { return ProgressWindow.makeProgressWindow(title, steps); } }; } private ModuleLoader getModuleLoader() { return applicationContext.getModuleLoader(); } @Override public ResourceUpdateHandler getResourceUpdateHandler() { return new DefaultFlexoResourceUpdateHandler(); } @Override public boolean isInteractive() { return true; } @Override public <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> A performAction( final A action, final EventObject e) { if (!action.getActionType().isEnabled(action.getFocusedObject(), action.getGlobalSelection())) { return null; } if (!(action instanceof FlexoGUIAction<?, ?, ?>) && action.getFocusedObject() != null && action.getFocusedObject().getProject() != getProject()) { if (logger.isLoggable(Level.INFO)) { logger.info("Cannot execute action because focused object is within another project than the one of this editor"); } return null; } executeAction(action, e); return action; } private <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> A executeAction( final A action, final EventObject event) { final boolean progressIsShowing = ProgressWindow.hasInstance(); boolean confirmDoAction = runInitializer(action, event); if (confirmDoAction) { actionWillBePerformed(action); if (action.isLongRunningAction() && SwingUtilities.isEventDispatchThread()) { ProgressWindow.showProgressWindow(action.getLocalizedName(), 100); SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { runAction(action); return null; } @Override protected void done() { super.done(); try { get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); if (e.getCause() instanceof FlexoException) { if (!runExceptionHandler((FlexoException) e.getCause(), action)) { if (!progressIsShowing) { ProgressWindow.hideProgressWindow(); } return; } } else { throw new RuntimeException(FlexoLocalization.localizedForKey("action_failed") + " " + action.getLocalizedName(), e.getCause()); } } runFinalizer(action, event); if (!progressIsShowing) { ProgressWindow.hideProgressWindow(); } } }; worker.execute(); return action; } else { try { runAction(action); } catch (FlexoException exception) { if (!runExceptionHandler(exception, action)) { return null; } } runFinalizer(action, event); if (!progressIsShowing) { ProgressWindow.hideProgressWindow(); } } } return action; } private <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> boolean runInitializer(A action, EventObject event) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(action.getActionType()); if (actionInitializer != null) { FlexoActionInitializer<A> initializer = actionInitializer.getDefaultInitializer(); if (initializer != null) { return initializer.run(event, action); } } return true; } private <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void runAction( final A action) throws FlexoException { if (getProject() != null) { getProject().clearRecentlyCreatedObjects(); } action.doActionInContext(); if (getProject() != null) { getProject().notifyRecentlyCreatedObjects(); } actionHasBeenPerformed(action, true); // Action succeeded } private <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void runFinalizer( final A action, EventObject event) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(action.getActionType()); if (actionInitializer != null) { FlexoActionFinalizer<A> finalizer = actionInitializer.getDefaultFinalizer(); if (finalizer != null) { finalizer.run(event, action); } } } private <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> boolean runExceptionHandler( FlexoException exception, final A action) { actionHasBeenPerformed(action, false); // Action failed ProgressWindow.hideProgressWindow(); FlexoExceptionHandler<A> exceptionHandler = null; ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(action.getActionType()); if (actionInitializer != null) { exceptionHandler = actionInitializer.getDefaultExceptionHandler(); } if (exceptionHandler != null) { if (exceptionHandler.handleException(exception, action)) { // The exception has been handled, we may still have to execute finalizer, if any return true; } else { return false; } } else { return false; } } @Override public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> A performUndoAction( final A action, final EventObject event) { boolean confirmUndoAction = true; ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(action.getActionType()); FlexoActionUndoInitializer<A> initializer = null; if (actionInitializer != null) { initializer = actionInitializer.getDefaultUndoInitializer(); if (initializer != null) { confirmUndoAction = initializer.run(event, action); } } if (confirmUndoAction) { actionWillBeUndone(action); try { if (getProject() != null) { getProject().clearRecentlyCreatedObjects(); } action.doActionInContext(); if (getProject() != null) { getProject().notifyRecentlyCreatedObjects(); } actionHasBeenUndone(action, true); // Action succeeded } catch (FlexoException exception) { actionHasBeenUndone(action, false); // Action failed ProgressWindow.hideProgressWindow(); FlexoExceptionHandler<A> exceptionHandler = null; if (actionInitializer != null) { exceptionHandler = actionInitializer.getDefaultExceptionHandler(); } if (exceptionHandler != null) { if (exceptionHandler.handleException(exception, action)) { // The exception has been handled, we may still have to execute finalizer, if any } else { return action; } } else { return action; } } FlexoActionUndoFinalizer<A> finalizer = null; if (actionInitializer != null) { finalizer = actionInitializer.getDefaultUndoFinalizer(); if (finalizer != null) { confirmUndoAction = finalizer.run(event, action); } } } ProgressWindow.hideProgressWindow(); return action; } @Override public <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> A performRedoAction( A action, EventObject event) { boolean confirmRedoAction = true; ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(action.getActionType()); FlexoActionRedoInitializer<A> initializer = null; if (actionInitializer != null) { initializer = actionInitializer.getDefaultRedoInitializer(); if (initializer != null) { confirmRedoAction = initializer.run(event, action); } } if (confirmRedoAction) { actionWillBeRedone(action); try { if (getProject() != null) { getProject().clearRecentlyCreatedObjects(); } action.redoActionInContext(); if (getProject() != null) { getProject().notifyRecentlyCreatedObjects(); } actionHasBeenRedone(action, true); // Action succeeded } catch (FlexoException exception) { actionHasBeenUndone(action, false); // Action failed ProgressWindow.hideProgressWindow(); FlexoExceptionHandler<A> exceptionHandler = null; if (actionInitializer != null) { exceptionHandler = actionInitializer.getDefaultExceptionHandler(); } if (exceptionHandler != null) { if (exceptionHandler.handleException(exception, action)) { // The exception has been handled, we may still have to execute finalizer, if any } else { return action; } } else { return action; } } FlexoActionRedoFinalizer<A> finalizer = null; if (actionInitializer != null) { finalizer = actionInitializer.getDefaultRedoFinalizer(); if (finalizer != null) { confirmRedoAction = finalizer.run(event, action); } } } ProgressWindow.hideProgressWindow(); return action; } @Override public UndoManager getUndoManager() { return _undoManager; } private <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionWillBePerformed( A action) { _undoManager.actionWillBePerformed(action); _currentlyPerformedActionStack.push(action); _createdAndNotNotifiedObjects.put(action, new Vector<FlexoModelObject>()); _deletedAndNotNotifiedObjects.put(action, new Vector<FlexoModelObject>()); } private <A extends org.openflexo.foundation.action.FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenPerformed( A action, boolean success) { _undoManager.actionHasBeenPerformed(action, success); if (success) { if (_scenarioRecorder != null) { if (!action.isEmbedded() || action.getOwnerAction().getExecutionStatus() != ExecutionStatus.EXECUTING_CORE) { _scenarioRecorder.registerDoneAction(action); } } } FlexoAction<?, ?, ?> popAction = _currentlyPerformedActionStack.pop(); if (popAction != action) { logger.warning("Expected to pop " + action + " but found " + popAction); } for (FlexoModelObject o : action.getExecutionContext().getObjectsCreatedWhileExecutingAction().values()) { _createdAndNotNotifiedObjects.get(action).remove(o); } for (FlexoModelObject o : action.getExecutionContext().getObjectsDeletedWhileExecutingAction().values()) { _deletedAndNotNotifiedObjects.get(action).remove(o); } if (WARN_MODEL_MODIFICATIONS_OUTSIDE_FLEXO_ACTION_LAYER) { for (FlexoModelObject o : _createdAndNotNotifiedObjects.get(action)) { logger.warning("FlexoModelObject " + o + " created during action " + action + " but was not notified (see objectCreated(String,FlexoModelObject))"); } for (FlexoModelObject o : _deletedAndNotNotifiedObjects.get(action)) { logger.warning("FlexoModelObject " + o + " deleted during action " + action + " but was not notified (see objectDeleted(String,FlexoModelObject))"); } } _createdAndNotNotifiedObjects.remove(action); _deletedAndNotNotifiedObjects.remove(action); } private <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionWillBeUndone( A action) { _undoManager.actionWillBeUndone(action); _currentlyUndoneActionStack.push(action); } private <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenUndone( A action, boolean success) { _undoManager.actionHasBeenUndone(action, success); FlexoAction<?, ?, ?> popAction = _currentlyUndoneActionStack.pop(); if (popAction != action) { logger.warning("Expected to pop " + action + " but found " + popAction); } } private <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionWillBeRedone( A action) { _undoManager.actionWillBeRedone(action); _currentlyRedoneActionStack.push(action); } private <A extends FlexoUndoableAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> void actionHasBeenRedone( A action, boolean success) { _undoManager.actionHasBeenRedone(action, success); FlexoAction<?, ?, ?> popAction = _currentlyRedoneActionStack.pop(); if (popAction != action) { logger.warning("Expected to pop " + action + " but found " + popAction); } } @Override public void notifyObjectCreated(FlexoModelObject object) { if (logger.isLoggable(Level.FINE)) { logger.fine("notifyObjectCreated: " + object); } if (_currentlyPerformedActionStack.isEmpty() && _currentlyUndoneActionStack.isEmpty() && _currentlyRedoneActionStack.isEmpty() && WARN_MODEL_MODIFICATIONS_OUTSIDE_FLEXO_ACTION_LAYER) { logger.warning("FlexoModelObject " + object + " created outside of FlexoAction context !!!"); } else if (!_currentlyPerformedActionStack.isEmpty()) { _createdAndNotNotifiedObjects.get(_currentlyPerformedActionStack.peek()).add(object); } object.setDocFormat(FlexoDocFormat.HTML, false); } @Override public void notifyObjectDeleted(FlexoModelObject object) { if (logger.isLoggable(Level.FINE)) { logger.fine("notifyObjectDeleted: " + object); } if (_currentlyPerformedActionStack.isEmpty() && _currentlyUndoneActionStack.isEmpty() && _currentlyRedoneActionStack.isEmpty() && WARN_MODEL_MODIFICATIONS_OUTSIDE_FLEXO_ACTION_LAYER) { logger.warning("FlexoModelObject " + object + " deleted outside of FlexoAction context !!!"); } else if (!_currentlyPerformedActionStack.isEmpty()) { _deletedAndNotNotifiedObjects.get(_currentlyPerformedActionStack.peek()).add(object); } } @Override public void notifyObjectChanged(FlexoModelObject object) { if (_currentlyPerformedActionStack.isEmpty() && _currentlyUndoneActionStack.isEmpty() && _currentlyRedoneActionStack.isEmpty() && WARN_MODEL_MODIFICATIONS_OUTSIDE_FLEXO_ACTION_LAYER) { logger.warning("setChanged() called for " + object + " outside of FlexoAction context !!!"); } } @Override public boolean performResourceScanning() { return true; } @Override public FlexoProgressFactory getFlexoProgressFactory() { return _progressFactory; } public boolean isTestEditor() { return false; } @Override public void focusOn(FlexoModelObject object) { try { if (object instanceof WKFObject) { getModuleLoader().switchToModule(Module.WKF_MODULE); } else if (object instanceof IEObject) { getModuleLoader().switchToModule(Module.IE_MODULE); } else if (object instanceof DMObject) { getModuleLoader().switchToModule(Module.DM_MODULE); } else if (object instanceof WSObject) { getModuleLoader().switchToModule(Module.WSE_MODULE); } else if (object instanceof ViewObject) { getModuleLoader().switchToModule(Module.VE_MODULE); } } catch (ModuleLoadingException e) { logger.warning("Cannot load module " + e.getModule()); e.printStackTrace(); } // Only interactive editor handle this getModuleLoader().getActiveModule().getFlexoController().setCurrentEditedObjectAsModuleView(object); getModuleLoader().getActiveModule().getFlexoController().getSelectionManager().setSelectedObject(object); } public void registerControllerActionInitializer(ControllerActionInitializer controllerActionInitializer) { actionInitializers.put(controllerActionInitializer.getModule(), controllerActionInitializer); } public void unregisterControllerActionInitializer(ControllerActionInitializer controllerActionInitializer) { actionInitializers.remove(controllerActionInitializer.getModule()); } private ControllerActionInitializer getCurrentControllerActionInitializer() { return actionInitializers.get(getModuleLoader().getActiveModule()); } private <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> ActionInitializer<A, T1, T2> getActionInitializer( FlexoActionType<A, T1, T2> actionType) { ControllerActionInitializer currentControllerActionInitializer = getCurrentControllerActionInitializer(); if (currentControllerActionInitializer != null) { return currentControllerActionInitializer.getActionInitializer(actionType); } return null; } @Override public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> boolean isActionEnabled( FlexoActionType<A, T1, T2> actionType, T1 focusedObject, Vector<T2> globalSelection) { if (actionType instanceof ActionSchemeActionType) { return true; } if (actionType.isEnabled(focusedObject, globalSelection)) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(actionType); if (actionInitializer != null) { FlexoActionEnableCondition<A, T1, T2> condition = actionInitializer.getEnableCondition(); if (condition != null) { return condition.isEnabled(actionType, focusedObject, globalSelection, this); } } else { return false; } } else { return false; } return true; } @Override public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> boolean isActionVisible( FlexoActionType<A, T1, T2> actionType, T1 focusedObject, Vector<T2> globalSelection) { if (actionType.isVisibleForSelection(focusedObject, globalSelection)) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(actionType); if (actionInitializer != null) { FlexoActionVisibleCondition<A, T1, T2> condition = actionInitializer.getVisibleCondition(); if (condition != null) { return condition.isVisible(actionType, focusedObject, globalSelection, this); } } else { return false; } } else { return false; } return true; } @Override public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> Icon getEnabledIconFor( FlexoActionType<A, T1, T2> actionType) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(actionType); if (actionInitializer != null) { return actionInitializer.getEnabledIcon(); } return null; } @Override public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> Icon getDisabledIconFor( FlexoActionType<A, T1, T2> actionType) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(actionType); if (actionInitializer != null) { return actionInitializer.getDisabledIcon(); } return null; } @Override public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> KeyStroke getKeyStrokeFor( FlexoActionType<A, T1, T2> actionType) { ActionInitializer<A, T1, T2> actionInitializer = getActionInitializer(actionType); if (actionInitializer != null) { return actionInitializer.getShortcut(); } return null; } }