org.openbp.cockpit.modeler.Modeler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openbp-cockpit Show documentation
Show all versions of openbp-cockpit Show documentation
OpenBP Cockpit (graphical process modeler)
The newest version!
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openbp.cockpit.modeler;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.Collections;
import java.util.List;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openbp.cockpit.modeler.drawing.DrawingEditorPlugin;
import org.openbp.cockpit.modeler.drawing.ProcessDrawing;
import org.openbp.cockpit.modeler.drawing.Trackable;
import org.openbp.cockpit.modeler.drawing.WorkspaceDrawingView;
import org.openbp.cockpit.modeler.figures.process.NodeFigure;
import org.openbp.cockpit.modeler.tools.ModelerToolSupport;
import org.openbp.cockpit.modeler.undo.ModelerUndoable;
import org.openbp.common.ExceptionUtil;
import org.openbp.common.listener.SwingListenerSupport;
import org.openbp.core.model.ModelObject;
import org.openbp.core.model.ModelQualifier;
import org.openbp.core.model.item.process.NodeProvider;
import org.openbp.core.model.item.process.ProcessItem;
import org.openbp.guiclient.model.ModelConnector;
import org.openbp.jaspira.action.ActionMgr;
import org.openbp.jaspira.action.JaspiraAction;
import org.openbp.jaspira.action.JaspiraActionEvent;
import org.openbp.jaspira.event.JaspiraEvent;
import org.openbp.jaspira.event.VetoableEvent;
import org.openbp.jaspira.gui.plugin.AbstractVisiblePlugin;
import org.openbp.jaspira.plugin.PluginState;
import org.openbp.jaspira.plugins.statusbar.StatusBarTextEvent;
import org.openbp.jaspira.undo.UndoMgr;
import org.openbp.jaspira.undo.Undoable;
import org.openbp.swing.components.JMsgBox;
import CH.ifa.draw.framework.DrawingView;
import CH.ifa.draw.framework.Tool;
import CH.ifa.draw.framework.ViewChangeListener;
import CH.ifa.draw.util.UndoManager;
/**
* The Modeler class is one of the core components of the OpenBP modeler.
* It is an invisible plugin that contains the drawing for a single OpenBP process.
*
* @author Stephan Moritz
*/
public class Modeler extends AbstractVisiblePlugin
implements DrawingEditorPlugin, Trackable, FocusListener
{
/**
* Runtime attribute for items: The item is an item skeleton that is used
* as a pattern for the creation of new items that are dragged from the standard toolbox.
*/
public static final String ATTRIBUTE_SKELETON = "_skeleton";
/** Process drawing associated with this modeler */
private ProcessDrawing drawing;
/** The model qualifier of the process edited by this editor */
private ModelQualifier processQualifier;
/** The JHotDraw View object we represent */
private WorkspaceDrawingView workspaceView;
/** the scroll pane of our View */
private JScrollPane scrollPane;
/** Clipboard support helper class */
private ClipboardSupport clipboardSupport;
/** Undo manager */
private UndoMgr undoMgr;
/** Current undoable */
private ModelerUndoable currentUndoable;
/** The modeler tool support object holding all tools */
private ModelerToolSupport toolSupport;
/** Listener support object holding the listeners */
private SwingListenerSupport listenerSupport;
/** If true, do not notify miniview about updates */
private boolean trackSuspended;
public String getResourceCollectionContainerName()
{
return "plugin.modeler";
}
/**
* Init the visual components of the plugin.
*/
protected void initializeComponents()
{
// Initialization will be done after the process has been set
}
/**
* Initializes the modeler after the process/drawing to edit has been set.
*/
private void initializeModeler()
{
workspaceView = new WorkspaceDrawingView(this);
workspaceView.setDrawing(drawing);
toolSupport = new ModelerToolSupport(this);
StandardToolSupportSetup.setupToolSupport(toolSupport, true);
clipboardSupport = new ClipboardSupport(workspaceView, getPluginResourceCollection(), false);
undoMgr = new UndoMgr();
getContentPane().removeAll();
scrollPane = new JScrollPane(workspaceView);
scrollPane.getViewport().addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
fireTrackChangedEvent(e);
}
});
getContentPane().add(scrollPane);
addPluginFocusListener(this);
fireEvent(new JaspiraEvent(this, "modeler.view.opened", this));
}
/**
* Gets the the modeler tool support object holding all tools.
*/
public ModelerToolSupport getToolSupport()
{
return toolSupport;
}
//////////////////////////////////////////////////
// @@ Various
//////////////////////////////////////////////////
/**
* Sets the process to be edited.
* The method clones the given process.
* @param process Process to be edited
*/
public void setProcess(ProcessItem process, boolean readonly)
{
ProcessItem clonedProcess = null;
try
{
clonedProcess = (ProcessItem) process.clone();
}
catch (Exception e)
{
// Shouldn't happen
ExceptionUtil.printTrace(e);
return;
}
// Make sure that the parent-child links and control and data links are valid
clonedProcess.maintainReferences(ModelObject.RESOLVE_LOCAL_REFS);
processQualifier = clonedProcess.getQualifier();
drawing = new ProcessDrawing(clonedProcess, this);
drawing.setReadOnly(readonly);
initializeModeler();
if (process != null)
{
// Update any plugins that refer to the current process
fireEvent("modeler.view.activated", this);
}
}
/**
* Returns the edited process.
*/
public ProcessItem getProcess()
{
return drawing != null ? drawing.getProcess() : null;
}
/**
* Gets the drawing view that displays the contents of this view plugin.
*/
public WorkspaceDrawingView getDrawingView()
{
return workspaceView;
}
/**
* Returns the drawing associated with this editor.
*/
public ProcessDrawing getDrawing()
{
return drawing;
}
/**
* Returns the model qualifier of the edited process.
*/
public ModelQualifier getProcessQualifier()
{
return processQualifier;
}
public boolean saveProcess()
{
drawing.encodeGeometry();
return ModelConnector.getInstance().saveItem(getProcess(), false);
}
public void repairDamage()
{
workspaceView.repairDamage();
}
//////////////////////////////////////////////////
// @@ Placeholder support
//////////////////////////////////////////////////
public boolean convertPlaceholder(NodeFigure nodeFigure, NodeProvider np, Point p)
{
return false;
}
//////////////////////////////////////////////////
// @@ Plugin overrides
//////////////////////////////////////////////////
protected Class [] getExternalEventModuleClasses()
{
return new Class [] { ModelerEventModule.class, ModelerInteractionModule.class };
}
protected void pluginUninstalled()
{
super.pluginUninstalled();
if (toolSupport != null)
{
toolSupport.deactivate();
}
// Disconnect the current process of the drawing from the actual process model.
// This is necessary because otherwise the process objects of the process item would
// reference figures of the drawing, which would not be used any more, but could also
// not be garbage collected.
if (drawing != null)
{
drawing.setProcess(null);
drawing.setEditor(null);
}
if (workspaceView != null)
{
// Dispose the drawing view
workspaceView.unregister();
workspaceView.removeFigureSelectionListener(this);
}
// Clear references for better garbage collection in case of memory leaks
drawing = null;
processQualifier = null;
workspaceView = null;
scrollPane = null;
clipboardSupport = null;
toolSupport = null;
}
public void setPluginState(PluginState state)
{
super.setPluginState(state);
}
public PluginState getPluginState()
{
return super.getPluginState();
}
public int getToolbarType()
{
return TOOLBAR_DYNAMIC;
}
public boolean canDrag()
{
return false;
}
public boolean hasCloseButton()
{
return true;
}
public boolean canClose()
{
if (drawing == null)
{
// This might be due to an out of memory error
return true;
}
VetoableEvent ve = new VetoableEvent(this, "modeler.view.askclose", this);
fireEvent(ve);
if (ve.isVetoed())
{
return false;
}
// TOLOCALIZE
ProcessItem process = getProcess();
if (process.isModified())
{
String msg = "" + process.getQualifier() + " has been modified. Save?";
int result = JMsgBox.show(null, msg, JMsgBox.TYPE_YESNOCANCEL);
if (result == JMsgBox.TYPE_CANCEL)
{
return false;
}
else if (result == JMsgBox.TYPE_YES)
{
if (!saveProcess())
{
// Save failed
return false;
}
fireEvent(new StatusBarTextEvent(this, "Process " + processQualifier + " saved."));
}
else
{
drawing.clearModified();
}
}
return true;
}
protected void preClose()
{
// Clear the selection, so the property browser get disconnected from the process objects
workspaceView.clearSelection();
fireEvent("modeler.view.closed", this);
}
public void pluginShown()
{
super.pluginShown();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
fireEvent("modeler.view.activated", Modeler.this);
fireEvent("modelerpage.view.showzoomfactor", new Double(workspaceView.getScaleFactor()));
drawing.updateModificationState();
}
});
toolSupport.updateToolState();
}
public void pluginHidden()
{
super.pluginHidden();
fireEvent("modeler.view.deactivated", this);
fireEvent("modelerpage.view.showzoomfactor", null);
}
public String getTitle()
{
if (drawing == null)
{
return super.getTitle();
}
return super.getTitle() + " - " + drawing.getDisplayText();
}
public String getSubTitle()
{
return processQualifier.toUntypedString();
}
public List getSubClients()
{
return Collections.singletonList(drawing);
}
//////////////////////////////////////////////////
// @@ FocusListener implementation
//////////////////////////////////////////////////
public void focusGained(FocusEvent e)
{
// Update the cut/copy/paste button status
fireEvent("global.clipboard.updatestatus");
// Update process modification state
if (drawing != null)
{
drawing.updateModificationState();
}
if (workspaceView != null)
{
workspaceView.updateSelection();
}
}
public void focusLost(FocusEvent e)
{
// Update the cut/copy/paste button status
fireEvent("global.clipboard.updatestatus");
// Update process modification state
if (drawing != null)
{
drawing.updateModificationState();
}
}
//////////////////////////////////////////////////
// @@ Trackable implementation
//////////////////////////////////////////////////
public Dimension getDocumentSize()
{
return workspaceView.getSize();
}
public Rectangle getVisibleArea()
{
// The the view rectangle
Rectangle r = scrollPane.getViewport().getViewRect();
// Convert to document coordinates
return workspaceView.applyScale(r, true);
}
public void setVisibleArea(Rectangle r)
{
workspaceView.setVisibleRect(r);
}
public void centerTrackerAt(Point p)
{
// Convert to component coordinates
p = workspaceView.applyScale(p, false);
Dimension d = scrollPane.getViewport().getSize();
p.x -= d.width / 2;
p.y -= d.height / 2;
// Limit tracking area, so we don't leave the view boundaries
if (p.x < 0)
{
p.x = 0;
}
if (p.y < 0)
{
p.y = 0;
}
if (p.x + d.width > workspaceView.getWidth())
{
p.x = workspaceView.getWidth() - d.width;
}
if (p.y + d.height > workspaceView.getHeight())
{
p.y = workspaceView.getHeight() - d.height;
}
scrollPane.getViewport().setViewPosition(p);
}
public void moveTrackerBy(int x, int y)
{
Rectangle r = getVisibleArea();
r.translate(x, y);
workspaceView.scrollRectToVisible(r);
}
/**
* The listener is registered for all properties as a WEAK listener, i. e. it may
* be garbage-collected if not referenced otherwise.
* ATTENTION: Never add an automatic class (i. e new FocusListener () { ... }) or an inner
* class that is not referenced otherwise as a weak listener to the list. These objects
* will be cleared by the garbage collector during the next gc run!
*/
public void addTrackChangedListener(ChangeListener listener)
{
if (listenerSupport == null)
{
listenerSupport = new SwingListenerSupport();
}
listenerSupport.addWeakListener(ChangeListener.class, listener);
}
public void removeTrackChangedListener(ChangeListener listener)
{
if (listenerSupport != null)
{
listenerSupport.removeListener(ChangeListener.class, listener);
}
}
public void fireTrackChangedEvent(ChangeEvent event)
{
if (!trackSuspended)
{
if (listenerSupport != null && listenerSupport.containsListeners(ChangeListener.class))
{
listenerSupport.fireStateChanged(event);
}
}
}
/**
* Prevents track change events to be propagated.
*/
public void suspendTrack()
{
trackSuspended = true;
}
/**
* Resumes propagation of track change events.
*/
public void resumeTrack()
{
trackSuspended = false;
fireTrackChangedEvent(new ChangeEvent(this));
}
/**
* Returns true if the tracking has been suspended.
*/
public boolean isTrackSuspended()
{
return trackSuspended;
}
/**
* Sets the scaling factor of the workspace.
*/
public void setScaleFactor(double scaleFactor)
{
workspaceView.setScaleFactor(scaleFactor);
}
/////////////////////////////////////////////////////////////////////////
// @@ Clipboard support
/////////////////////////////////////////////////////////////////////////
public boolean canCopy()
{
return clipboardSupport != null ? clipboardSupport.canCopy() : false;
}
public boolean canDelete()
{
return clipboardSupport != null ? clipboardSupport.canDelete() : false;
}
public boolean canCut()
{
return clipboardSupport != null ? clipboardSupport.canCut() : false;
}
public boolean canPaste(Transferable transferable)
{
return clipboardSupport != null ? clipboardSupport.canPaste(transferable) : false;
}
public Transferable copy()
{
return clipboardSupport.getCopyData();
}
public Transferable cut()
{
startUndo("Cut Selection");
Transferable result = clipboardSupport.cut();
endUndo();
return result;
}
public void delete()
{
startUndo("Delete Selection");
clipboardSupport.delete();
endUndo();
}
public void paste(Transferable transferable)
{
startUndo("Paste");
clipboardSupport.paste(transferable);
endUndo();
}
//////////////////////////////////////////////////
// @@ Undo support
//////////////////////////////////////////////////
/**
* Creates an undoable object given the display name of the operation that can be undone with this undoable.
* The undoable returned will be a {@link ModelerUndoable} that contains a copy of the current process.
* The method will also save the returned undoable so it can be retrieved with {@link #getCurrentUndoable}.
* In order to provide the data after the operation and to register the undoable, call the {@link #endUndo} method.
*
* @param displayName Display name or null
* This text will appear after the 'undo: ' text in the edit menu.
* @return The new undoable
*/
public Undoable startUndo(String displayName)
{
if (currentUndoable != null)
{
// Commit the last transaction
endUndo();
}
ModelerUndoable undoable = new ModelerUndoable(this);
undoable.setDisplayName(displayName);
currentUndoable = undoable;
return undoable;
}
/**
* Creates an undoable object given the name of the action that initiated the operation that can be undone with this undoable.
* For further details, see {@link #startUndo}.
*
* @param eventName Event name of the action event
* @return The new undoable
*/
public Undoable startUndoForAction(String eventName)
{
String displayName = null;
JaspiraAction action = ActionMgr.getInstance().getAction(eventName);
if (action != null)
{
displayName = action.getDisplayName();
}
return startUndo(displayName);
}
/**
* Creates an undoable object given the name of the action that initiated the operation that can be undone with this undoable.
* For further details, see {@link #startUndo}.
*
* @param jae Action event
* @return The new undoable
*/
public Undoable startUndoForAction(JaspiraActionEvent jae)
{
return startUndoForAction(jae.getEventName());
}
/**
* Updates the current undoable with the current 'after operation' state and registers it with the undo manager.
* This method may be called only after the {@link #startUndo} method has been called.
*/
public void endUndo()
{
if (currentUndoable != null)
{
getDrawing().setModified();
undoMgr.registerUndoable(currentUndoable);
currentUndoable = null;
}
}
/**
* Cancels the current undoable.
* This method may be called only after the {@link #startUndo} method has been called.
*/
public void cancelUndo()
{
currentUndoable = null;
}
/**
* Checks if currently an undo operation is being recorded.
*
* @return
* true: {@link #startUndo} was called.
* false: No current undoable is present.
*/
public boolean isUndoRecording()
{
return currentUndoable != null;
}
/**
* Gets the current undoable.
* @return The undoable or null if the method is not being called between {@link #startUndo} and {@link #endUndo}/{@link #cancelUndo}.
*/
public Undoable getCurrentUndoable()
{
return currentUndoable;
}
/**
* Sets the process to be edited due to an undo or redo operation.
* The method clones the given process.
* @param process Process to be edited
*/
public void setProcessByUndoRedo(ProcessItem process)
{
// Clone the process
try
{
process = (ProcessItem) process.clone();
}
catch (Exception e)
{
// Shouldn't happen
ExceptionUtil.printTrace(e);
}
process.maintainReferences(ModelObject.RESOLVE_LOCAL_REFS);
// Update the drawing.
// This will also decode the geometry data.
drawing.setProcess(process);
// Redraw the view
fireEvent("modeler.view.redraw");
}
/**
* Gets the undo manager.
*/
public UndoMgr getUndoMgr()
{
return undoMgr;
}
/////////////////////////////////////////////////////////////////////////
// @@ DrawingEditorPlugin implementation
/////////////////////////////////////////////////////////////////////////
public void addViewChangeListener(ViewChangeListener arg0)
{
}
public void figureSelectionChanged(DrawingView arg0)
{
}
public UndoManager getUndoManager()
{
return null;
}
public void removeViewChangeListener(ViewChangeListener arg0)
{
}
public void showStatus(String arg0)
{
fireEvent("global.status", arg0);
}
public Tool tool()
{
return toolSupport;
}
public void toolDone()
{
}
public DrawingView view()
{
return workspaceView;
}
public DrawingView [] views()
{
return new DrawingView [] { workspaceView };
}
}